Queue and UI fixes

This commit is contained in:
2026-03-05 11:04:20 +00:00
parent 23acea4773
commit e3d890c071
103 changed files with 11400 additions and 2010 deletions

11
.claude/settings.json Normal file
View File

@@ -0,0 +1,11 @@
{
"permissions": {
"allow": [
"Bash(ls /home/michael/ripster/*.sh)",
"Bash(ls /home/michael/ripster/backend/*.sh)",
"Bash(systemctl list-units --type=service)",
"Bash(pip install -q -r requirements-docs.txt)",
"Bash(mkdocs build --strict)"
]
}
}

1
.venv-docs/bin/python Symbolic link
View File

@@ -0,0 +1 @@
python3

1
.venv-docs/bin/python3 Symbolic link
View File

@@ -0,0 +1 @@
/usr/bin/python3

1
.venv-docs/bin/python3.12 Symbolic link
View File

@@ -0,0 +1 @@
python3

1
.venv-docs/lib64 Symbolic link
View File

@@ -0,0 +1 @@
lib

5
.venv-docs/pyvenv.cfg Normal file
View File

@@ -0,0 +1,5 @@
home = /usr/bin
include-system-site-packages = false
version = 3.12.3
executable = /usr/bin/python3.12
command = /usr/bin/python3 -m venv /home/michael/ripster/.venv-docs

View File

@@ -95,11 +95,13 @@ router.post(
const selectedEncodeTitleId = req.body?.selectedEncodeTitleId ?? null; const selectedEncodeTitleId = req.body?.selectedEncodeTitleId ?? null;
const selectedTrackSelection = req.body?.selectedTrackSelection ?? null; const selectedTrackSelection = req.body?.selectedTrackSelection ?? null;
const selectedPostEncodeScriptIds = req.body?.selectedPostEncodeScriptIds; const selectedPostEncodeScriptIds = req.body?.selectedPostEncodeScriptIds;
const skipPipelineStateUpdate = Boolean(req.body?.skipPipelineStateUpdate);
logger.info('post:confirm-encode', { logger.info('post:confirm-encode', {
reqId: req.reqId, reqId: req.reqId,
jobId, jobId,
selectedEncodeTitleId, selectedEncodeTitleId,
selectedTrackSelectionProvided: Boolean(selectedTrackSelection), selectedTrackSelectionProvided: Boolean(selectedTrackSelection),
skipPipelineStateUpdate,
selectedPostEncodeScriptIdsCount: Array.isArray(selectedPostEncodeScriptIds) selectedPostEncodeScriptIdsCount: Array.isArray(selectedPostEncodeScriptIds)
? selectedPostEncodeScriptIds.length ? selectedPostEncodeScriptIds.length
: 0 : 0
@@ -107,7 +109,8 @@ router.post(
const job = await pipelineService.confirmEncodeReview(jobId, { const job = await pipelineService.confirmEncodeReview(jobId, {
selectedEncodeTitleId, selectedEncodeTitleId,
selectedTrackSelection, selectedTrackSelection,
selectedPostEncodeScriptIds selectedPostEncodeScriptIds,
skipPipelineStateUpdate
}); });
res.json({ job }); res.json({ job });
}) })
@@ -116,9 +119,13 @@ router.post(
router.post( router.post(
'/cancel', '/cancel',
asyncHandler(async (req, res) => { asyncHandler(async (req, res) => {
logger.warn('post:cancel', { reqId: req.reqId }); const rawJobId = req.body?.jobId;
await pipelineService.cancel(); const jobId = rawJobId === null || rawJobId === undefined || String(rawJobId).trim() === ''
res.json({ ok: true }); ? null
: Number(rawJobId);
logger.warn('post:cancel', { reqId: req.reqId, jobId });
const result = await pipelineService.cancel(jobId);
res.json({ result });
}) })
); );
@@ -127,8 +134,8 @@ router.post(
asyncHandler(async (req, res) => { asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId); const jobId = Number(req.params.jobId);
logger.info('post:retry', { reqId: req.reqId, jobId }); logger.info('post:retry', { reqId: req.reqId, jobId });
await pipelineService.retry(jobId); const result = await pipelineService.retry(jobId);
res.json({ ok: true }); res.json({ result });
}) })
); );
@@ -152,6 +159,16 @@ router.post(
}) })
); );
router.post(
'/restart-review/:jobId',
asyncHandler(async (req, res) => {
const jobId = Number(req.params.jobId);
logger.info('post:restart-review', { reqId: req.reqId, jobId });
const result = await pipelineService.restartReviewFromRaw(jobId);
res.json({ result });
})
);
router.post( router.post(
'/restart-encode/:jobId', '/restart-encode/:jobId',
asyncHandler(async (req, res) => { asyncHandler(async (req, res) => {
@@ -162,4 +179,23 @@ router.post(
}) })
); );
router.get(
'/queue',
asyncHandler(async (req, res) => {
logger.debug('get:queue', { reqId: req.reqId });
const queue = await pipelineService.getQueueSnapshot();
res.json({ queue });
})
);
router.post(
'/queue/reorder',
asyncHandler(async (req, res) => {
const orderedJobIds = Array.isArray(req.body?.orderedJobIds) ? req.body.orderedJobIds : [];
logger.info('post:queue:reorder', { reqId: req.reqId, orderedJobIds });
const queue = await pipelineService.reorderQueue(orderedJobIds);
res.json({ queue });
})
);
module.exports = router; module.exports = router;

View File

@@ -564,6 +564,49 @@ class HistoryService {
})); }));
} }
async getJobsByIds(jobIds = []) {
const ids = Array.isArray(jobIds)
? jobIds
.map((value) => Number(value))
.filter((value) => Number.isFinite(value) && value > 0)
.map((value) => Math.trunc(value))
: [];
if (ids.length === 0) {
return [];
}
const db = await getDb();
const placeholders = ids.map(() => '?').join(', ');
const rows = await db.all(
`SELECT * FROM jobs WHERE id IN (${placeholders})`,
ids
);
const byId = new Map(rows.map((row) => [Number(row.id), row]));
return ids
.map((id) => byId.get(id))
.filter(Boolean)
.map((job) => ({
...enrichJobRow(job),
log_count: hasProcessLogFile(job.id) ? 1 : 0
}));
}
async getRunningJobs() {
const db = await getDb();
const rows = await db.all(
`
SELECT *
FROM jobs
WHERE status IN ('RIPPING', 'ENCODING')
ORDER BY updated_at ASC, id ASC
`
);
return rows.map((job) => ({
...enrichJobRow(job),
log_count: hasProcessLogFile(job.id) ? 1 : 0
}));
}
async getJobWithLogs(jobId, options = {}) { async getJobWithLogs(jobId, options = {}) {
const db = await getDb(); const db = await getDb();
const job = await db.get('SELECT * FROM jobs WHERE id = ?', [jobId]); const job = await db.get('SELECT * FROM jobs WHERE id = ?', [jobId]);

File diff suppressed because it is too large Load Diff

View File

@@ -71,18 +71,23 @@ function spawnTrackedProcess({
}); });
}); });
let cancelCalled = false;
const cancel = () => { const cancel = () => {
if (child.killed) { if (cancelCalled) {
return; return;
} }
cancelCalled = true;
logger.warn('spawn:cancel:requested', { cmd, args, context, pid: child.pid }); logger.warn('spawn:cancel:requested', { cmd, args, context, pid: child.pid });
child.kill('SIGINT'); child.kill('SIGINT');
setTimeout(() => { setTimeout(() => {
if (!child.killed) { try {
process.kill(child.pid, 0);
logger.warn('spawn:cancel:force-kill', { cmd, args, context, pid: child.pid }); logger.warn('spawn:cancel:force-kill', { cmd, args, context, pid: child.pid });
child.kill('SIGKILL'); child.kill('SIGKILL');
} catch (_e) {
// Process already terminated
} }
}, 3000); }, 3000);
}; };

View File

@@ -70,6 +70,20 @@ function normalizeTrackIds(rawList) {
return output; return output;
} }
function normalizeNonNegativeInteger(rawValue) {
if (rawValue === null || rawValue === undefined) {
return null;
}
if (typeof rawValue === 'string' && rawValue.trim() === '') {
return null;
}
const value = Number(rawValue);
if (!Number.isFinite(value) || value < 0) {
return null;
}
return Math.trunc(value);
}
function removeSelectionArgs(extraArgs) { function removeSelectionArgs(extraArgs) {
const args = Array.isArray(extraArgs) ? extraArgs : []; const args = Array.isArray(extraArgs) ? extraArgs : [];
const filtered = []; const filtered = [];
@@ -554,7 +568,7 @@ class SettingsService {
? 'backup' ? 'backup'
: 'mkv'; : 'mkv';
const sourceArg = this.resolveSourceArg(map, deviceInfo); const sourceArg = this.resolveSourceArg(map, deviceInfo);
const rawSelectedTitleId = Number(options?.selectedTitleId); const rawSelectedTitleId = normalizeNonNegativeInteger(options?.selectedTitleId);
const parsedExtra = splitArgs(map.makemkv_rip_extra_args); const parsedExtra = splitArgs(map.makemkv_rip_extra_args);
let extra = []; let extra = [];
let baseArgs = []; let baseArgs = [];
@@ -574,7 +588,7 @@ class SettingsService {
} else { } else {
extra = parsedExtra; extra = parsedExtra;
const minLength = Number(map.makemkv_min_length_minutes || 60); const minLength = Number(map.makemkv_min_length_minutes || 60);
const hasExplicitTitle = Number.isFinite(rawSelectedTitleId) && rawSelectedTitleId >= 0; const hasExplicitTitle = rawSelectedTitleId !== null;
const targetTitle = hasExplicitTitle ? String(Math.trunc(rawSelectedTitleId)) : 'all'; const targetTitle = hasExplicitTitle ? String(Math.trunc(rawSelectedTitleId)) : 'all';
if (hasExplicitTitle) { if (hasExplicitTitle) {
baseArgs = [ baseArgs = [

View File

@@ -524,13 +524,15 @@ function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {})
const similarityGroups = buildSimilarityGroups(candidates, durationSimilaritySeconds); const similarityGroups = buildSimilarityGroups(candidates, durationSimilaritySeconds);
const obfuscationDetected = similarityGroups.length > 0; const obfuscationDetected = similarityGroups.length > 0;
const primaryGroup = similarityGroups[0] || null; const multipleCandidatesDetected = candidates.length > 1;
const evaluatedCandidates = primaryGroup ? scoreCandidates(primaryGroup.titles) : []; const manualDecisionRequired = multipleCandidatesDetected;
const decisionPool = manualDecisionRequired ? candidates : [];
const evaluatedCandidates = decisionPool.length > 0 ? scoreCandidates(decisionPool) : [];
const recommendation = evaluatedCandidates[0] || null; const recommendation = evaluatedCandidates[0] || null;
const candidatePlaylists = primaryGroup const candidatePlaylists = manualDecisionRequired
? uniqueOrdered(primaryGroup.titles.map((item) => item.playlistId).filter(Boolean)) ? uniqueOrdered(decisionPool.map((item) => item.playlistId).filter(Boolean))
: []; : [];
const playlistSegments = buildPlaylistSegmentMap(primaryGroup ? primaryGroup.titles : []); const playlistSegments = buildPlaylistSegmentMap(decisionPool);
const playlistToTitleId = buildPlaylistToTitleIdMap(parsedTitles); const playlistToTitleId = buildPlaylistToTitleIdMap(parsedTitles);
return { return {
@@ -542,7 +544,10 @@ function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {})
candidates, candidates,
duplicateDurationGroups: similarityGroups, duplicateDurationGroups: similarityGroups,
obfuscationDetected, obfuscationDetected,
manualDecisionRequired: obfuscationDetected, manualDecisionRequired,
manualDecisionReason: manualDecisionRequired
? (obfuscationDetected ? 'multiple_similar_candidates' : 'multiple_candidates_after_min_length')
: null,
candidatePlaylists, candidatePlaylists,
candidatePlaylistFiles: candidatePlaylists.map((item) => `${item}.mpls`), candidatePlaylistFiles: candidatePlaylists.map((item) => `${item}.mpls`),
playlistToTitleId, playlistToTitleId,

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,13 @@ function App() {
})); }));
} }
if (message.type === 'PIPELINE_QUEUE_CHANGED') {
setPipeline((prev) => ({
...(prev || {}),
queue: message.payload || null
}));
}
if (message.type === 'DISC_DETECTED') { if (message.type === 'DISC_DETECTED') {
setLastDiscEvent(message.payload?.device || null); setLastDiscEvent(message.payload?.device || null);
} }

View File

@@ -115,9 +115,10 @@ export const api = {
body: JSON.stringify(payload || {}) body: JSON.stringify(payload || {})
}); });
}, },
cancelPipeline() { cancelPipeline(jobId = null) {
return request('/pipeline/cancel', { return request('/pipeline/cancel', {
method: 'POST' method: 'POST',
body: JSON.stringify({ jobId })
}); });
}, },
retryJob(jobId) { retryJob(jobId) {
@@ -135,11 +136,25 @@ export const api = {
method: 'POST' method: 'POST'
}); });
}, },
restartReviewFromRaw(jobId) {
return request(`/pipeline/restart-review/${jobId}`, {
method: 'POST'
});
},
restartEncodeWithLastSettings(jobId) { restartEncodeWithLastSettings(jobId) {
return request(`/pipeline/restart-encode/${jobId}`, { return request(`/pipeline/restart-encode/${jobId}`, {
method: 'POST' method: 'POST'
}); });
}, },
getPipelineQueue() {
return request('/pipeline/queue');
},
reorderPipelineQueue(orderedJobIds = []) {
return request('/pipeline/queue/reorder', {
method: 'POST',
body: JSON.stringify({ orderedJobIds: Array.isArray(orderedJobIds) ? orderedJobIds : [] })
});
},
getJobs(params = {}) { getJobs(params = {}) {
const query = new URLSearchParams(); const query = new URLSearchParams();
if (params.status) query.set('status', params.status); if (params.status) query.set('status', params.status);

View File

@@ -3,6 +3,7 @@ import { Button } from 'primereact/button';
import MediaInfoReviewPanel from './MediaInfoReviewPanel'; import MediaInfoReviewPanel from './MediaInfoReviewPanel';
import blurayIndicatorIcon from '../assets/media-bluray.svg'; import blurayIndicatorIcon from '../assets/media-bluray.svg';
import discIndicatorIcon from '../assets/media-disc.svg'; import discIndicatorIcon from '../assets/media-disc.svg';
import { getStatusLabel } from '../utils/statusPresentation';
function JsonView({ title, value }) { function JsonView({ title, value }) {
return ( return (
@@ -18,36 +19,43 @@ function resolveMediaType(job) {
return raw === 'bluray' ? 'bluray' : 'disc'; return raw === 'bluray' ? 'bluray' : 'disc';
} }
function statusBadgeMeta(status) { function statusBadgeMeta(status, queued = false) {
const normalized = String(status || '').trim().toUpperCase(); const normalized = String(status || '').trim().toUpperCase();
const label = getStatusLabel(normalized, { queued });
if (queued) {
return { label, icon: 'pi-list', tone: 'info' };
}
if (normalized === 'FINISHED') { if (normalized === 'FINISHED') {
return { label: normalized, icon: 'pi-check-circle', tone: 'success' }; return { label, icon: 'pi-check-circle', tone: 'success' };
} }
if (normalized === 'ERROR') { if (normalized === 'ERROR') {
return { label: normalized, icon: 'pi-times-circle', tone: 'danger' }; return { label, icon: 'pi-times-circle', tone: 'danger' };
}
if (normalized === 'CANCELLED') {
return { label, icon: 'pi-ban', tone: 'warning' };
} }
if (normalized === 'READY_TO_ENCODE' || normalized === 'READY_TO_START') { if (normalized === 'READY_TO_ENCODE' || normalized === 'READY_TO_START') {
return { label: normalized, icon: 'pi-play-circle', tone: 'info' }; return { label, icon: 'pi-play-circle', tone: 'info' };
} }
if (normalized === 'WAITING_FOR_USER_DECISION') { if (normalized === 'WAITING_FOR_USER_DECISION') {
return { label: normalized, icon: 'pi-exclamation-circle', tone: 'warning' }; return { label, icon: 'pi-exclamation-circle', tone: 'warning' };
} }
if (normalized === 'METADATA_SELECTION') { if (normalized === 'METADATA_SELECTION') {
return { label: normalized, icon: 'pi-list', tone: 'warning' }; return { label, icon: 'pi-list', tone: 'warning' };
} }
if (normalized === 'ANALYZING') { if (normalized === 'ANALYZING') {
return { label: normalized, icon: 'pi-search', tone: 'warning' }; return { label, icon: 'pi-search', tone: 'warning' };
} }
if (normalized === 'RIPPING') { if (normalized === 'RIPPING') {
return { label: normalized, icon: 'pi-download', tone: 'warning' }; return { label, icon: 'pi-download', tone: 'warning' };
} }
if (normalized === 'MEDIAINFO_CHECK') { if (normalized === 'MEDIAINFO_CHECK') {
return { label: normalized, icon: 'pi-sliders-h', tone: 'warning' }; return { label, icon: 'pi-sliders-h', tone: 'warning' };
} }
if (normalized === 'ENCODING') { if (normalized === 'ENCODING') {
return { label: normalized, icon: 'pi-cog', tone: 'warning' }; return { label, icon: 'pi-cog', tone: 'warning' };
} }
return { label: normalized || '-', icon: 'pi-info-circle', tone: 'secondary' }; return { label: label || '-', icon: 'pi-info-circle', tone: 'secondary' };
} }
function omdbField(value) { function omdbField(value) {
@@ -82,10 +90,14 @@ export default function JobDetailDialog({
onLoadLog, onLoadLog,
logLoadingMode = null, logLoadingMode = null,
onAssignOmdb, onAssignOmdb,
onResumeReady,
onRestartEncode, onRestartEncode,
onRestartReview,
onReencode, onReencode,
onDeleteFiles, onDeleteFiles,
onDeleteEntry, onDeleteEntry,
onRemoveFromQueue,
isQueued = false,
omdbAssignBusy = false, omdbAssignBusy = false,
actionBusy = false, actionBusy = false,
reencodeBusy = false, reencodeBusy = false,
@@ -95,6 +107,11 @@ export default function JobDetailDialog({
const running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'].includes(job?.status); const running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'].includes(job?.status);
const showFinalLog = !running; const showFinalLog = !running;
const canReencode = !!(job?.rawStatus?.exists && job?.rawStatus?.isEmpty !== true && mkDone && !running); const canReencode = !!(job?.rawStatus?.exists && job?.rawStatus?.isEmpty !== true && mkDone && !running);
const canResumeReady = Boolean(
(String(job?.status || '').trim().toUpperCase() === 'READY_TO_ENCODE' || String(job?.last_state || '').trim().toUpperCase() === 'READY_TO_ENCODE')
&& !running
&& typeof onResumeReady === 'function'
);
const hasConfirmedPlan = Boolean( const hasConfirmedPlan = Boolean(
job?.encodePlan job?.encodePlan
&& Array.isArray(job?.encodePlan?.titles) && Array.isArray(job?.encodePlan?.titles)
@@ -103,7 +120,13 @@ export default function JobDetailDialog({
); );
const hasRestartInput = Boolean(job?.encode_input_path || job?.raw_path || job?.encodePlan?.encodeInputPath); const hasRestartInput = Boolean(job?.encode_input_path || job?.raw_path || job?.encodePlan?.encodeInputPath);
const canRestartEncode = Boolean(hasConfirmedPlan && hasRestartInput && !running); const canRestartEncode = Boolean(hasConfirmedPlan && hasRestartInput && !running);
const canRestartReview = Boolean(
(job?.rawStatus?.exists || job?.raw_path)
&& !running
&& typeof onRestartReview === 'function'
);
const canDeleteEntry = !running && typeof onDeleteEntry === 'function'; const canDeleteEntry = !running && typeof onDeleteEntry === 'function';
const queueLocked = Boolean(isQueued && job?.id);
const logCount = Number(job?.log_count || 0); const logCount = Number(job?.log_count || 0);
const logMeta = job?.logMeta && typeof job.logMeta === 'object' ? job.logMeta : null; const logMeta = job?.logMeta && typeof job.logMeta === 'object' ? job.logMeta : null;
const logLoaded = Boolean(logMeta?.loaded) || Boolean(job?.log); const logLoaded = Boolean(logMeta?.loaded) || Boolean(job?.log);
@@ -112,7 +135,7 @@ export default function JobDetailDialog({
const mediaTypeLabel = mediaType === 'bluray' ? 'Blu-ray' : 'Sonstiges Medium'; const mediaTypeLabel = mediaType === 'bluray' ? 'Blu-ray' : 'Sonstiges Medium';
const mediaTypeIcon = mediaType === 'bluray' ? blurayIndicatorIcon : discIndicatorIcon; const mediaTypeIcon = mediaType === 'bluray' ? blurayIndicatorIcon : discIndicatorIcon;
const mediaTypeAlt = mediaType === 'bluray' ? 'Blu-ray' : 'Disc'; const mediaTypeAlt = mediaType === 'bluray' ? 'Blu-ray' : 'Disc';
const statusMeta = statusBadgeMeta(job?.status); const statusMeta = statusBadgeMeta(job?.status, queueLocked);
const omdbInfo = job?.omdbInfo && typeof job.omdbInfo === 'object' ? job.omdbInfo : {}; const omdbInfo = job?.omdbInfo && typeof job.omdbInfo === 'object' ? job.omdbInfo : {};
return ( return (
@@ -261,6 +284,19 @@ export default function JobDetailDialog({
<h4>Aktionen</h4> <h4>Aktionen</h4>
<div className="actions-row"> <div className="actions-row">
{queueLocked ? (
<Button
label="Aus Queue löschen"
icon="pi pi-times"
severity="danger"
outlined
size="small"
onClick={() => onRemoveFromQueue?.(job)}
loading={actionBusy}
disabled={typeof onRemoveFromQueue !== 'function'}
/>
) : (
<>
<Button <Button
label="OMDb neu zuordnen" label="OMDb neu zuordnen"
icon="pi pi-search" icon="pi pi-search"
@@ -270,6 +306,17 @@ export default function JobDetailDialog({
loading={omdbAssignBusy} loading={omdbAssignBusy}
disabled={running || typeof onAssignOmdb !== 'function'} disabled={running || typeof onAssignOmdb !== 'function'}
/> />
{canResumeReady ? (
<Button
label="Im Dashboard öffnen"
icon="pi pi-window-maximize"
severity="info"
outlined
size="small"
onClick={() => onResumeReady?.(job)}
loading={actionBusy}
/>
) : null}
{typeof onRestartEncode === 'function' ? ( {typeof onRestartEncode === 'function' ? (
<Button <Button
label="Encode neu starten" label="Encode neu starten"
@@ -281,6 +328,18 @@ export default function JobDetailDialog({
disabled={!canRestartEncode} disabled={!canRestartEncode}
/> />
) : null} ) : null}
{typeof onRestartReview === 'function' ? (
<Button
label="Review neu starten"
icon="pi pi-refresh"
severity="info"
outlined
size="small"
onClick={() => onRestartReview?.(job)}
loading={actionBusy}
disabled={!canRestartReview}
/>
) : null}
<Button <Button
label="RAW neu encodieren" label="RAW neu encodieren"
icon="pi pi-cog" icon="pi pi-cog"
@@ -329,6 +388,8 @@ export default function JobDetailDialog({
loading={deleteEntryBusy} loading={deleteEntryBusy}
disabled={!canDeleteEntry} disabled={!canDeleteEntry}
/> />
</>
)}
</div> </div>
<h4>Log</h4> <h4>Log</h4>

View File

@@ -51,6 +51,20 @@ function normalizeTrackIdList(values) {
return output; return output;
} }
function isBurnedSubtitleTrack(track) {
const flags = Array.isArray(track?.subtitlePreviewFlags)
? track.subtitlePreviewFlags
: (Array.isArray(track?.flags) ? track.flags : []);
const hasBurnedFlag = flags.some((flag) => String(flag || '').trim().toLowerCase() === 'burned');
const summary = `${track?.subtitlePreviewSummary || ''} ${track?.subtitleActionSummary || ''}`;
return Boolean(
track?.subtitlePreviewBurnIn
|| track?.burnIn
|| hasBurnedFlag
|| /burned/i.test(summary)
);
}
function splitArgs(input) { function splitArgs(input) {
if (!input || typeof input !== 'string') { if (!input || typeof input !== 'string') {
return []; return [];
@@ -542,8 +556,9 @@ function TrackList({
<div className="media-track-list"> <div className="media-track-list">
{tracks.map((track) => { {tracks.map((track) => {
const trackId = normalizeTrackId(track.id); const trackId = normalizeTrackId(track.id);
const burned = type === 'subtitle' ? isBurnedSubtitleTrack(track) : false;
const checked = allowSelection const checked = allowSelection
? (trackId !== null && selectedIds.includes(trackId)) ? (trackId !== null && selectedIds.includes(trackId) && !(type === 'subtitle' && burned))
: Boolean(track.selectedForEncode); : Boolean(track.selectedForEncode);
const selectedIndex = trackId !== null const selectedIndex = trackId !== null
? checkedTrackOrder.indexOf(trackId) ? checkedTrackOrder.indexOf(trackId)
@@ -567,26 +582,13 @@ function TrackList({
})() })()
: 'Nicht übernommen') : 'Nicht übernommen')
: null; : null;
const subtitleFlags = type === 'subtitle' && checked
? (Array.isArray(track.subtitlePreviewFlags)
? track.subtitlePreviewFlags
: (Array.isArray(track.flags) ? track.flags : []))
: [];
const displayLanguage = toLang2(track.language || track.languageLabel || 'und'); const displayLanguage = toLang2(track.language || track.languageLabel || 'und');
const displayHint = track.description || track.title; const displayHint = track.description || track.title;
const displayCodec = simplifyCodec(type, track.format, displayHint); const displayCodec = simplifyCodec(type, track.format, displayHint);
const displayChannelCount = channelCount(track.channels); const displayChannelCount = channelCount(track.channels);
const displayAudioTitle = audioChannelLabel(track.channels); const displayAudioTitle = audioChannelLabel(track.channels);
const audioVariant = type === 'audio' ? extractAudioVariant(displayHint) : ''; const audioVariant = type === 'audio' ? extractAudioVariant(displayHint) : '';
const burned = type === 'subtitle' && checked const disabled = !allowSelection || (type === 'subtitle' && burned);
? Boolean(
track.subtitlePreviewBurnIn
|| track.burnIn
|| subtitleFlags.includes('burned')
|| /burned/i.test(String(track.subtitlePreviewSummary || track.subtitleActionSummary || ''))
)
: false;
let displayText = `#${track.id} | ${displayLanguage} | ${displayCodec}`; let displayText = `#${track.id} | ${displayLanguage} | ${displayCodec}`;
if (type === 'audio') { if (type === 'audio') {
@@ -611,13 +613,13 @@ function TrackList({
type="checkbox" type="checkbox"
checked={checked} checked={checked}
onChange={(event) => { onChange={(event) => {
if (!allowSelection || typeof onToggleTrack !== 'function' || trackId === null) { if (disabled || typeof onToggleTrack !== 'function' || trackId === null) {
return; return;
} }
onToggleTrack(trackId, event.target.checked); onToggleTrack(trackId, event.target.checked);
}} }}
readOnly={!allowSelection} readOnly={disabled}
disabled={!allowSelection} disabled={disabled}
/> />
<span>{displayText}</span> <span>{displayText}</span>
</label> </label>
@@ -848,12 +850,18 @@ export default function MediaInfoReviewPanel({
? currentSelectedId === normalizeTitleId(title.id) ? currentSelectedId === normalizeTitleId(title.id)
: Boolean(title.selectedForEncode); : Boolean(title.selectedForEncode);
const titleSelectionEntry = trackSelectionByTitle?.[title.id] || trackSelectionByTitle?.[String(title.id)] || {}; const titleSelectionEntry = trackSelectionByTitle?.[title.id] || trackSelectionByTitle?.[String(title.id)] || {};
const subtitleTracks = Array.isArray(title.subtitleTracks) ? title.subtitleTracks : [];
const selectableSubtitleTrackIds = subtitleTracks
.filter((track) => !isBurnedSubtitleTrack(track))
.map((track) => normalizeTrackId(track?.id))
.filter((id) => id !== null);
const selectableSubtitleTrackIdSet = new Set(selectableSubtitleTrackIds.map((id) => String(id)));
const defaultAudioTrackIds = (Array.isArray(title.audioTracks) ? title.audioTracks : []) const defaultAudioTrackIds = (Array.isArray(title.audioTracks) ? title.audioTracks : [])
.filter((track) => Boolean(track?.selectedByRule)) .filter((track) => Boolean(track?.selectedByRule))
.map((track) => normalizeTrackId(track?.id)) .map((track) => normalizeTrackId(track?.id))
.filter((id) => id !== null); .filter((id) => id !== null);
const defaultSubtitleTrackIds = (Array.isArray(title.subtitleTracks) ? title.subtitleTracks : []) const defaultSubtitleTrackIds = subtitleTracks
.filter((track) => Boolean(track?.selectedByRule)) .filter((track) => Boolean(track?.selectedByRule) && !isBurnedSubtitleTrack(track))
.map((track) => normalizeTrackId(track?.id)) .map((track) => normalizeTrackId(track?.id))
.filter((id) => id !== null); .filter((id) => id !== null);
const selectedAudioTrackIds = normalizeTrackIdList( const selectedAudioTrackIds = normalizeTrackIdList(
@@ -865,7 +873,7 @@ export default function MediaInfoReviewPanel({
Array.isArray(titleSelectionEntry?.subtitleTrackIds) Array.isArray(titleSelectionEntry?.subtitleTrackIds)
? titleSelectionEntry.subtitleTrackIds ? titleSelectionEntry.subtitleTrackIds
: defaultSubtitleTrackIds : defaultSubtitleTrackIds
); ).filter((id) => selectableSubtitleTrackIdSet.has(String(id)));
const allowTrackSelectionForTitle = Boolean( const allowTrackSelectionForTitle = Boolean(
allowTrackSelection allowTrackSelection
&& allowTitleSelection && allowTitleSelection
@@ -934,7 +942,7 @@ export default function MediaInfoReviewPanel({
/> />
<TrackList <TrackList
title={`Subtitles (Titel #${title.id})`} title={`Subtitles (Titel #${title.id})`}
tracks={title.subtitleTracks || []} tracks={allowTrackSelectionForTitle ? subtitleTracks.filter((track) => !isBurnedSubtitleTrack(track)) : subtitleTracks}
type="subtitle" type="subtitle"
allowSelection={allowTrackSelectionForTitle} allowSelection={allowTrackSelectionForTitle}
selectedTrackIds={selectedSubtitleTrackIds} selectedTrackIds={selectedSubtitleTrackIds}

View File

@@ -5,21 +5,7 @@ import { ProgressBar } from 'primereact/progressbar';
import { Button } from 'primereact/button'; import { Button } from 'primereact/button';
import MediaInfoReviewPanel from './MediaInfoReviewPanel'; import MediaInfoReviewPanel from './MediaInfoReviewPanel';
import { api } from '../api/client'; import { api } from '../api/client';
import { getStatusLabel, getStatusSeverity } from '../utils/statusPresentation';
const severityMap = {
IDLE: 'success',
DISC_DETECTED: 'info',
ANALYZING: 'warning',
METADATA_SELECTION: 'warning',
WAITING_FOR_USER_DECISION: 'warning',
READY_TO_START: 'info',
MEDIAINFO_CHECK: 'warning',
READY_TO_ENCODE: 'info',
RIPPING: 'warning',
ENCODING: 'warning',
FINISHED: 'success',
ERROR: 'danger'
};
function normalizeTitleId(value) { function normalizeTitleId(value) {
const parsed = Number(value); const parsed = Number(value);
@@ -92,6 +78,20 @@ function normalizeScriptIdList(values) {
return output; return output;
} }
function isBurnedSubtitleTrack(track) {
const flags = Array.isArray(track?.subtitlePreviewFlags)
? track.subtitlePreviewFlags
: (Array.isArray(track?.flags) ? track.flags : []);
const hasBurnedFlag = flags.some((flag) => String(flag || '').trim().toLowerCase() === 'burned');
const summary = `${track?.subtitlePreviewSummary || ''} ${track?.subtitleActionSummary || ''}`;
return Boolean(
track?.subtitlePreviewBurnIn
|| track?.burnIn
|| hasBurnedFlag
|| /burned/i.test(summary)
);
}
function buildDefaultTrackSelection(review) { function buildDefaultTrackSelection(review) {
const titles = Array.isArray(review?.titles) ? review.titles : []; const titles = Array.isArray(review?.titles) ? review.titles : [];
const selection = {}; const selection = {};
@@ -110,7 +110,7 @@ function buildDefaultTrackSelection(review) {
), ),
subtitleTrackIds: normalizeTrackIdList( subtitleTrackIds: normalizeTrackIdList(
(Array.isArray(title?.subtitleTracks) ? title.subtitleTracks : []) (Array.isArray(title?.subtitleTracks) ? title.subtitleTracks : [])
.filter((track) => Boolean(track?.selectedByRule)) .filter((track) => Boolean(track?.selectedByRule) && !isBurnedSubtitleTrack(track))
.map((track) => track?.id) .map((track) => track?.id)
) )
}; };
@@ -192,19 +192,25 @@ export default function PipelineStatusCard({
pipeline, pipeline,
onAnalyze, onAnalyze,
onReanalyze, onReanalyze,
onOpenMetadata,
onStart, onStart,
onRemoveFromQueue,
onRestartEncode, onRestartEncode,
onRestartReview,
onConfirmReview, onConfirmReview,
onSelectPlaylist, onSelectPlaylist,
onCancel, onCancel,
onRetry, onRetry,
isQueued = false,
busy, busy,
liveJobLog = '' liveJobLog = ''
}) { }) {
const state = pipeline?.state || 'IDLE'; const state = pipeline?.state || 'IDLE';
const stateLabel = getStatusLabel(state);
const progress = Number(pipeline?.progress || 0); const progress = Number(pipeline?.progress || 0);
const running = state === 'ANALYZING' || state === 'RIPPING' || state === 'ENCODING' || state === 'MEDIAINFO_CHECK'; const running = state === 'ANALYZING' || state === 'RIPPING' || state === 'ENCODING' || state === 'MEDIAINFO_CHECK';
const retryJobId = pipeline?.context?.jobId; const retryJobId = pipeline?.context?.jobId;
const queueLocked = Boolean(isQueued && retryJobId);
const selectedMetadata = pipeline?.context?.selectedMetadata || null; const selectedMetadata = pipeline?.context?.selectedMetadata || null;
const mediaInfoReview = pipeline?.context?.mediaInfoReview || null; const mediaInfoReview = pipeline?.context?.mediaInfoReview || null;
const playlistAnalysis = pipeline?.context?.playlistAnalysis || null; const playlistAnalysis = pipeline?.context?.playlistAnalysis || null;
@@ -298,10 +304,15 @@ export default function PipelineStatusCard({
? Boolean(retryJobId) ? Boolean(retryJobId)
: Boolean(retryJobId && encodeInputPath); : Boolean(retryJobId && encodeInputPath);
const canRestartEncodeFromLastSettings = Boolean( const canRestartEncodeFromLastSettings = Boolean(
state === 'ERROR' (state === 'ERROR' || state === 'CANCELLED')
&& retryJobId && retryJobId
&& pipeline?.context?.canRestartEncodeFromLastSettings && pipeline?.context?.canRestartEncodeFromLastSettings
); );
const canRestartReviewFromRaw = Boolean(
retryJobId
&& !running
&& (pipeline?.context?.canRestartReviewFromRaw || pipeline?.context?.rawPath)
);
const waitingPlaylistRows = useMemo(() => { const waitingPlaylistRows = useMemo(() => {
const evaluated = Array.isArray(playlistAnalysis?.evaluatedCandidates) const evaluated = Array.isArray(playlistAnalysis?.evaluatedCandidates)
@@ -401,11 +412,24 @@ export default function PipelineStatusCard({
? defaultTrackSelectionForTitle(mediaInfoReview, encodeTitleId) ? defaultTrackSelectionForTitle(mediaInfoReview, encodeTitleId)
: { audioTrackIds: [], subtitleTrackIds: [] }; : { audioTrackIds: [], subtitleTrackIds: [] };
const effectiveSelection = selectionEntry || fallbackSelection; const effectiveSelection = selectionEntry || fallbackSelection;
const encodeTitle = encodeTitleId
? (Array.isArray(mediaInfoReview?.titles)
? (mediaInfoReview.titles.find((title) => normalizeTitleId(title?.id) === encodeTitleId) || null)
: null)
: null;
const blockedSubtitleTrackIds = new Set(
(Array.isArray(encodeTitle?.subtitleTracks) ? encodeTitle.subtitleTracks : [])
.filter((track) => isBurnedSubtitleTrack(track))
.map((track) => normalizeTrackId(track?.id))
.filter((id) => id !== null)
.map((id) => String(id))
);
const selectedTrackSelection = encodeTitleId const selectedTrackSelection = encodeTitleId
? { ? {
[encodeTitleId]: { [encodeTitleId]: {
audioTrackIds: normalizeTrackIdList(effectiveSelection?.audioTrackIds || []), audioTrackIds: normalizeTrackIdList(effectiveSelection?.audioTrackIds || []),
subtitleTrackIds: normalizeTrackIdList(effectiveSelection?.subtitleTrackIds || []) subtitleTrackIds: normalizeTrackIdList(effectiveSelection?.subtitleTrackIds || [])
.filter((id) => !blockedSubtitleTrackIds.has(String(id)))
} }
} }
: null; : null;
@@ -418,9 +442,9 @@ export default function PipelineStatusCard({
}; };
return ( return (
<Card title="Pipeline Status" subTitle="Live Zustand und Fortschritt"> <Card title="Pipeline-Status" subTitle="Live-Zustand und Fortschritt">
<div className="status-row"> <div className="status-row">
<Tag value={state} severity={severityMap[state] || 'secondary'} /> <Tag value={stateLabel} severity={getStatusSeverity(state)} />
<span>{pipeline?.statusText || 'Bereit'}</span> <span>{pipeline?.statusText || 'Bereit'}</span>
</div> </div>
@@ -438,6 +462,18 @@ export default function PipelineStatusCard({
)} )}
<div className="actions-row"> <div className="actions-row">
{queueLocked ? (
<Button
label="Aus Queue löschen"
icon="pi pi-times"
severity="danger"
outlined
onClick={() => onRemoveFromQueue?.(retryJobId)}
loading={busy}
disabled={typeof onRemoveFromQueue !== 'function'}
/>
) : (
<>
{(state === 'DISC_DETECTED' || state === 'IDLE') && ( {(state === 'DISC_DETECTED' || state === 'IDLE') && (
<Button <Button
label="Analyse starten" label="Analyse starten"
@@ -447,7 +483,17 @@ export default function PipelineStatusCard({
/> />
)} )}
{state === 'READY_TO_START' && retryJobId && ( {(state === 'METADATA_SELECTION' || state === 'WAITING_FOR_USER_DECISION') && retryJobId && typeof onOpenMetadata === 'function' ? (
<Button
label="Metadaten öffnen"
icon="pi pi-list"
severity="info"
onClick={() => onOpenMetadata?.(retryJobId)}
loading={busy}
/>
) : null}
{state === 'READY_TO_START' && retryJobId ? (
<Button <Button
label="Job starten" label="Job starten"
icon="pi pi-play" icon="pi pi-play"
@@ -455,7 +501,7 @@ export default function PipelineStatusCard({
onClick={() => onStart(retryJobId)} onClick={() => onStart(retryJobId)}
loading={busy} loading={busy}
/> />
)} ) : null}
{playlistDecisionRequiredBeforeStart && retryJobId && ( {playlistDecisionRequiredBeforeStart && retryJobId && (
<Button <Button
@@ -469,7 +515,7 @@ export default function PipelineStatusCard({
/> />
)} )}
{state === 'READY_TO_ENCODE' && retryJobId && ( {state === 'READY_TO_ENCODE' && retryJobId ? (
<Button <Button
label={isPreRipReview ? 'Backup + Encoding starten' : 'Encoding starten'} label={isPreRipReview ? 'Backup + Encoding starten' : 'Encoding starten'}
icon="pi pi-play" icon="pi pi-play"
@@ -496,18 +542,30 @@ export default function PipelineStatusCard({
loading={busy} loading={busy}
disabled={!canStartReadyJob || !canConfirmReview} disabled={!canStartReadyJob || !canConfirmReview}
/> />
)} ) : null}
{running && ( {running && (
<Button <Button
label="Abbrechen" label="Abbrechen"
icon="pi pi-stop" icon="pi pi-stop"
severity="danger" severity="danger"
onClick={onCancel} onClick={() => onCancel?.(retryJobId, state)}
loading={busy} loading={busy}
/> />
)} )}
{canRestartReviewFromRaw ? (
<Button
label="Review neu starten"
icon="pi pi-refresh"
severity="info"
outlined
onClick={() => onRestartReview?.(retryJobId)}
loading={busy}
disabled={!retryJobId}
/>
) : null}
{canRestartEncodeFromLastSettings ? ( {canRestartEncodeFromLastSettings ? (
<Button <Button
label="Encode neu starten" label="Encode neu starten"
@@ -519,7 +577,7 @@ export default function PipelineStatusCard({
/> />
) : null} ) : null}
{state === 'ERROR' && retryJobId && ( {(state === 'ERROR' || state === 'CANCELLED') && retryJobId && (
<Button <Button
label="Retry Rippen" label="Retry Rippen"
icon="pi pi-refresh" icon="pi pi-refresh"
@@ -529,7 +587,7 @@ export default function PipelineStatusCard({
/> />
)} )}
{state === 'ERROR' ? ( {(state === 'ERROR' || state === 'CANCELLED') ? (
<Button <Button
label="Disk-Analyse neu starten" label="Disk-Analyse neu starten"
icon="pi pi-search" icon="pi pi-search"
@@ -538,6 +596,8 @@ export default function PipelineStatusCard({
loading={busy} loading={busy}
/> />
) : null} ) : null}
</>
)}
</div> </div>
{running ? ( {running ? (
@@ -547,7 +607,7 @@ export default function PipelineStatusCard({
</div> </div>
) : null} ) : null}
{playlistDecisionRequiredBeforeStart ? ( {playlistDecisionRequiredBeforeStart && !queueLocked ? (
<div className="playlist-decision-block"> <div className="playlist-decision-block">
<h3>Playlist-Auswahl erforderlich</h3> <h3>Playlist-Auswahl erforderlich</h3>
<small> <small>
@@ -561,6 +621,7 @@ export default function PipelineStatusCard({
<input <input
type="checkbox" type="checkbox"
checked={normalizePlaylistId(selectedPlaylistId) === row.playlistId} checked={normalizePlaylistId(selectedPlaylistId) === row.playlistId}
disabled={queueLocked}
onChange={() => { onChange={() => {
const next = normalizePlaylistId(selectedPlaylistId) === row.playlistId ? null : row.playlistId; const next = normalizePlaylistId(selectedPlaylistId) === row.playlistId ? null : row.playlistId;
setSelectedPlaylistId(next); setSelectedPlaylistId(next);
@@ -629,7 +690,7 @@ export default function PipelineStatusCard({
<strong>IMDb:</strong> {selectedMetadata.imdbId || '-'} <strong>IMDb:</strong> {selectedMetadata.imdbId || '-'}
</div> </div>
<div> <div>
<strong>Status:</strong> {state} <strong>Status:</strong> {stateLabel}
</div> </div>
</div> </div>
</div> </div>
@@ -638,7 +699,7 @@ export default function PipelineStatusCard({
{(state === 'READY_TO_ENCODE' || state === 'MEDIAINFO_CHECK' || mediaInfoReview) ? ( {(state === 'READY_TO_ENCODE' || state === 'MEDIAINFO_CHECK' || mediaInfoReview) ? (
<div className="mediainfo-review-block"> <div className="mediainfo-review-block">
<h3>Titel-/Spurprüfung</h3> <h3>Titel-/Spurprüfung</h3>
{state === 'READY_TO_ENCODE' && !reviewConfirmed ? ( {state === 'READY_TO_ENCODE' && !reviewConfirmed && !queueLocked ? (
<small> <small>
{isPreRipReview {isPreRipReview
? 'Spurauswahl kann direkt übernommen werden. Beim Klick auf "Backup + Encoding starten" wird automatisch bestätigt und gestartet.' ? 'Spurauswahl kann direkt übernommen werden. Beim Klick auf "Backup + Encoding starten" wird automatisch bestätigt und gestartet.'
@@ -651,9 +712,9 @@ export default function PipelineStatusCard({
presetDisplayValue={presetDisplayValue} presetDisplayValue={presetDisplayValue}
commandOutputPath={commandOutputPath} commandOutputPath={commandOutputPath}
selectedEncodeTitleId={normalizeTitleId(selectedEncodeTitleId)} selectedEncodeTitleId={normalizeTitleId(selectedEncodeTitleId)}
allowTitleSelection={state === 'READY_TO_ENCODE' && !reviewConfirmed} allowTitleSelection={state === 'READY_TO_ENCODE' && !reviewConfirmed && !queueLocked}
onSelectEncodeTitle={(titleId) => setSelectedEncodeTitleId(normalizeTitleId(titleId))} onSelectEncodeTitle={(titleId) => setSelectedEncodeTitleId(normalizeTitleId(titleId))}
allowTrackSelection={state === 'READY_TO_ENCODE' && !reviewConfirmed} allowTrackSelection={state === 'READY_TO_ENCODE' && !reviewConfirmed && !queueLocked}
trackSelectionByTitle={trackSelectionByTitle} trackSelectionByTitle={trackSelectionByTitle}
onTrackSelectionChange={(titleId, trackType, trackId, checked) => { onTrackSelectionChange={(titleId, trackType, trackId, checked) => {
const normalizedTitleId = normalizeTitleId(titleId); const normalizedTitleId = normalizeTitleId(titleId);
@@ -684,7 +745,7 @@ export default function PipelineStatusCard({
}} }}
availablePostScripts={scriptCatalog} availablePostScripts={scriptCatalog}
selectedPostEncodeScriptIds={selectedPostEncodeScriptIds} selectedPostEncodeScriptIds={selectedPostEncodeScriptIds}
allowPostScriptSelection={state === 'READY_TO_ENCODE' && !reviewConfirmed} allowPostScriptSelection={state === 'READY_TO_ENCODE' && !reviewConfirmed && !queueLocked}
onAddPostEncodeScript={() => { onAddPostEncodeScript={() => {
setSelectedPostEncodeScriptIds((prev) => { setSelectedPostEncodeScriptIds((prev) => {
const normalizedCurrent = normalizeScriptIdList(prev); const normalizedCurrent = normalizeScriptIdList(prev);

View File

@@ -10,6 +10,7 @@ import PipelineStatusCard from '../components/PipelineStatusCard';
import MetadataSelectionDialog from '../components/MetadataSelectionDialog'; import MetadataSelectionDialog from '../components/MetadataSelectionDialog';
import blurayIndicatorIcon from '../assets/media-bluray.svg'; import blurayIndicatorIcon from '../assets/media-bluray.svg';
import discIndicatorIcon from '../assets/media-disc.svg'; import discIndicatorIcon from '../assets/media-disc.svg';
import { getStatusLabel, getStatusSeverity, normalizeStatus } from '../utils/statusPresentation';
const processingStates = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING']; const processingStates = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING'];
const dashboardStatuses = new Set([ const dashboardStatuses = new Set([
@@ -21,23 +22,9 @@ const dashboardStatuses = new Set([
'READY_TO_ENCODE', 'READY_TO_ENCODE',
'RIPPING', 'RIPPING',
'ENCODING', 'ENCODING',
'CANCELLED',
'ERROR' 'ERROR'
]); ]);
const statusSeverityMap = {
IDLE: 'secondary',
DISC_DETECTED: 'info',
ANALYZING: 'warning',
METADATA_SELECTION: 'warning',
WAITING_FOR_USER_DECISION: 'warning',
READY_TO_START: 'info',
MEDIAINFO_CHECK: 'warning',
READY_TO_ENCODE: 'info',
RIPPING: 'warning',
ENCODING: 'warning',
FINISHED: 'success',
ERROR: 'danger'
};
function normalizeJobId(value) { function normalizeJobId(value) {
const parsed = Number(value); const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed <= 0) { if (!Number.isFinite(parsed) || parsed <= 0) {
@@ -46,6 +33,54 @@ function normalizeJobId(value) {
return Math.trunc(parsed); return Math.trunc(parsed);
} }
function normalizeQueue(queue) {
const payload = queue && typeof queue === 'object' ? queue : {};
const runningJobs = Array.isArray(payload.runningJobs) ? payload.runningJobs : [];
const queuedJobs = Array.isArray(payload.queuedJobs) ? payload.queuedJobs : [];
return {
maxParallelJobs: Number(payload.maxParallelJobs || 1),
runningCount: Number(payload.runningCount || runningJobs.length || 0),
runningJobs,
queuedJobs,
queuedCount: Number(payload.queuedCount || queuedJobs.length || 0),
updatedAt: payload.updatedAt || null
};
}
function getQueueActionResult(response) {
return response?.result && typeof response.result === 'object' ? response.result : {};
}
function showQueuedToast(toastRef, actionLabel, result) {
if (!toastRef?.current) {
return;
}
const queuePosition = Number(result?.queuePosition || 0);
const positionText = queuePosition > 0 ? `Position ${queuePosition}` : 'in der Warteschlange';
toastRef.current.show({
severity: 'info',
summary: `${actionLabel} in Queue`,
detail: `${actionLabel} wurde ${positionText} eingeplant.`,
life: 3200
});
}
function reorderQueuedItems(items, draggedJobId, targetJobId) {
const list = Array.isArray(items) ? items : [];
const from = list.findIndex((item) => Number(item?.jobId) === Number(draggedJobId));
const to = list.findIndex((item) => Number(item?.jobId) === Number(targetJobId));
if (from < 0 || to < 0 || from === to) {
return list;
}
const next = [...list];
const [moved] = next.splice(from, 1);
next.splice(to, 0, moved);
return next.map((item, index) => ({
...item,
position: index + 1
}));
}
function getAnalyzeContext(job) { function getAnalyzeContext(job) {
return job?.makemkvInfo?.analyzeContext && typeof job.makemkvInfo.analyzeContext === 'object' return job?.makemkvInfo?.analyzeContext && typeof job.makemkvInfo.analyzeContext === 'object'
? job.makemkvInfo.analyzeContext ? job.makemkvInfo.analyzeContext
@@ -99,14 +134,12 @@ function JobStepChecks({ backupSuccess, encodeSuccess }) {
function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) { function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
const jobId = normalizeJobId(job?.id); const jobId = normalizeJobId(job?.id);
if ( const isCurrentSessionJob = Boolean(
jobId jobId
&& currentPipelineJobId && currentPipelineJobId
&& jobId === currentPipelineJobId && jobId === currentPipelineJobId
&& String(currentPipeline?.state || '').trim().toUpperCase() !== 'IDLE' && String(currentPipeline?.state || '').trim().toUpperCase() !== 'IDLE'
) { );
return currentPipeline;
}
const encodePlan = job?.encodePlan && typeof job.encodePlan === 'object' ? job.encodePlan : null; const encodePlan = job?.encodePlan && typeof job.encodePlan === 'object' ? job.encodePlan : null;
const analyzeContext = getAnalyzeContext(job); const analyzeContext = getAnalyzeContext(job);
@@ -124,11 +157,11 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
const errorText = String(job?.error_message || '').trim().toUpperCase(); const errorText = String(job?.error_message || '').trim().toUpperCase();
const hasOutputPath = Boolean(String(job?.output_path || '').trim()); const hasOutputPath = Boolean(String(job?.output_path || '').trim());
const hasEncodePlan = Boolean(encodePlan && Array.isArray(encodePlan?.titles) && encodePlan.titles.length > 0); const hasEncodePlan = Boolean(encodePlan && Array.isArray(encodePlan?.titles) && encodePlan.titles.length > 0);
const looksLikeCancelledEncode = jobStatus === 'ERROR' && ( const looksLikeCancelledEncode = (jobStatus === 'ERROR' || jobStatus === 'CANCELLED') && (
(errorText.includes('ABGEBROCHEN') || errorText.includes('CANCELLED')) (errorText.includes('ABGEBROCHEN') || errorText.includes('CANCELLED'))
&& (hasOutputPath || Boolean(job?.encode_input_path) || Boolean(job?.handbrakeInfo)) && (hasOutputPath || Boolean(job?.encode_input_path) || Boolean(job?.handbrakeInfo))
); );
const looksLikeEncodingError = jobStatus === 'ERROR' && ( const looksLikeEncodingError = (jobStatus === 'ERROR' || jobStatus === 'CANCELLED') && (
errorText.includes('ENCODING') errorText.includes('ENCODING')
|| errorText.includes('HANDBRAKE') || errorText.includes('HANDBRAKE')
|| lastState === 'ENCODING' || lastState === 'ENCODING'
@@ -142,18 +175,18 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
&& ( && (
jobStatus === 'READY_TO_ENCODE' jobStatus === 'READY_TO_ENCODE'
|| jobStatus === 'ENCODING' || jobStatus === 'ENCODING'
|| jobStatus === 'CANCELLED'
|| looksLikeEncodingError || looksLikeEncodingError
) )
); );
const canRestartReviewFromRaw = Boolean(
return { job?.raw_path
state: jobStatus, && !processingStates.includes(jobStatus)
activeJobId: jobId, );
progress: Number.isFinite(Number(job?.progress)) ? Number(job.progress) : 0, const computedContext = {
eta: job?.eta || null,
statusText: job?.status_text || job?.error_message || null,
context: {
jobId, jobId,
rawPath: job?.raw_path || null,
detectedTitle: job?.detected_title || null,
inputPath, inputPath,
hasEncodableTitle, hasEncodableTitle,
reviewConfirmed, reviewConfirmed,
@@ -173,20 +206,54 @@ function buildPipelineFromJob(job, currentPipeline, currentPipelineJobId) {
: [], : [],
selectedPlaylist: analyzeContext.selectedPlaylist || null, selectedPlaylist: analyzeContext.selectedPlaylist || null,
selectedTitleId: analyzeContext.selectedTitleId ?? null, selectedTitleId: analyzeContext.selectedTitleId ?? null,
canRestartEncodeFromLastSettings omdbCandidates: [],
canRestartEncodeFromLastSettings,
canRestartReviewFromRaw
};
if (isCurrentSessionJob) {
const existingContext = currentPipeline?.context && typeof currentPipeline.context === 'object'
? currentPipeline.context
: {};
return {
...currentPipeline,
context: {
...computedContext,
...existingContext,
rawPath: existingContext.rawPath || computedContext.rawPath,
selectedMetadata: existingContext.selectedMetadata || computedContext.selectedMetadata,
canRestartEncodeFromLastSettings:
existingContext.canRestartEncodeFromLastSettings ?? computedContext.canRestartEncodeFromLastSettings,
canRestartReviewFromRaw:
existingContext.canRestartReviewFromRaw ?? computedContext.canRestartReviewFromRaw
} }
}; };
}
return {
state: jobStatus,
activeJobId: jobId,
progress: Number.isFinite(Number(job?.progress)) ? Number(job.progress) : 0,
eta: job?.eta || null,
statusText: job?.status_text || job?.error_message || null,
context: computedContext
};
} }
export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline }) { export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline }) {
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
const [metadataDialogVisible, setMetadataDialogVisible] = useState(false); const [metadataDialogVisible, setMetadataDialogVisible] = useState(false);
const [metadataDialogContext, setMetadataDialogContext] = useState(null);
const [cancelCleanupDialog, setCancelCleanupDialog] = useState({ const [cancelCleanupDialog, setCancelCleanupDialog] = useState({
visible: false, visible: false,
jobId: null, jobId: null,
outputPath: null target: null,
path: null
}); });
const [cancelCleanupBusy, setCancelCleanupBusy] = useState(false); const [cancelCleanupBusy, setCancelCleanupBusy] = useState(false);
const [queueState, setQueueState] = useState(() => normalizeQueue(pipeline?.queue));
const [queueReorderBusy, setQueueReorderBusy] = useState(false);
const [draggingQueueJobId, setDraggingQueueJobId] = useState(null);
const [liveJobLog, setLiveJobLog] = useState(''); const [liveJobLog, setLiveJobLog] = useState('');
const [jobsLoading, setJobsLoading] = useState(false); const [jobsLoading, setJobsLoading] = useState(false);
const [dashboardJobs, setDashboardJobs] = useState([]); const [dashboardJobs, setDashboardJobs] = useState([]);
@@ -200,8 +267,16 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
const loadDashboardJobs = async () => { const loadDashboardJobs = async () => {
setJobsLoading(true); setJobsLoading(true);
try { try {
const response = await api.getJobs(); const [jobsResponse, queueResponse] = await Promise.allSettled([
const allJobs = Array.isArray(response?.jobs) ? response.jobs : []; api.getJobs(),
api.getPipelineQueue()
]);
const allJobs = jobsResponse.status === 'fulfilled'
? (Array.isArray(jobsResponse.value?.jobs) ? jobsResponse.value.jobs : [])
: [];
if (queueResponse.status === 'fulfilled') {
setQueueState(normalizeQueue(queueResponse.value?.queue));
}
const next = allJobs const next = allJobs
.filter((job) => dashboardStatuses.has(String(job?.status || '').trim().toUpperCase())) .filter((job) => dashboardStatuses.has(String(job?.status || '').trim().toUpperCase()))
.sort((a, b) => Number(b?.id || 0) - Number(a?.id || 0)); .sort((a, b) => Number(b?.id || 0) - Number(a?.id || 0));
@@ -237,10 +312,20 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
}; };
useEffect(() => { useEffect(() => {
if (!metadataDialogVisible) {
return;
}
if (metadataDialogContext?.jobId) {
return;
}
if (pipeline?.state !== 'METADATA_SELECTION' && pipeline?.state !== 'WAITING_FOR_USER_DECISION') { if (pipeline?.state !== 'METADATA_SELECTION' && pipeline?.state !== 'WAITING_FOR_USER_DECISION') {
setMetadataDialogVisible(false); setMetadataDialogVisible(false);
} }
}, [pipeline?.state]); }, [pipeline?.state, metadataDialogVisible, metadataDialogContext?.jobId]);
useEffect(() => {
setQueueState(normalizeQueue(pipeline?.queue));
}, [pipeline?.queue]);
useEffect(() => { useEffect(() => {
void loadDashboardJobs(); void loadDashboardJobs();
@@ -303,6 +388,71 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
return map; return map;
}, [dashboardJobs, pipeline, currentPipelineJobId]); }, [dashboardJobs, pipeline, currentPipelineJobId]);
const buildMetadataContextForJob = (jobId) => {
const normalizedJobId = normalizeJobId(jobId);
if (!normalizedJobId) {
return null;
}
const job = dashboardJobs.find((item) => normalizeJobId(item?.id) === normalizedJobId) || null;
const pipelineForJob = pipelineByJobId.get(normalizedJobId) || null;
const context = pipelineForJob?.context && typeof pipelineForJob.context === 'object'
? pipelineForJob.context
: {};
const selectedMetadata = context.selectedMetadata && typeof context.selectedMetadata === 'object'
? context.selectedMetadata
: {
title: job?.title || job?.detected_title || context?.detectedTitle || '',
year: job?.year || null,
imdbId: job?.imdb_id || null,
poster: job?.poster_url || null
};
return {
...context,
jobId: normalizedJobId,
detectedTitle: context?.detectedTitle || job?.detected_title || selectedMetadata?.title || '',
selectedMetadata,
omdbCandidates: Array.isArray(context?.omdbCandidates) ? context.omdbCandidates : []
};
};
const defaultMetadataDialogContext = useMemo(() => {
const currentState = String(pipeline?.state || '').trim().toUpperCase();
const currentContext = pipeline?.context && typeof pipeline.context === 'object'
? pipeline.context
: null;
const currentContextJobId = normalizeJobId(currentContext?.jobId);
if (
(currentState === 'METADATA_SELECTION' || currentState === 'WAITING_FOR_USER_DECISION')
&& currentContextJobId
) {
return {
...currentContext,
jobId: currentContextJobId,
selectedMetadata: currentContext?.selectedMetadata || {
title: currentContext?.detectedTitle || '',
year: null,
imdbId: null,
poster: null
},
omdbCandidates: Array.isArray(currentContext?.omdbCandidates) ? currentContext.omdbCandidates : []
};
}
const pendingJob = dashboardJobs.find((job) => {
const normalized = normalizeStatus(job?.status);
return normalized === 'METADATA_SELECTION' || normalized === 'WAITING_FOR_USER_DECISION';
});
if (!pendingJob) {
return null;
}
return buildMetadataContextForJob(pendingJob.id);
}, [pipeline, dashboardJobs, pipelineByJobId]);
const effectiveMetadataDialogContext = metadataDialogContext
|| defaultMetadataDialogContext
|| pipeline?.context
|| {};
const showError = (error) => { const showError = (error) => {
toastRef.current?.show({ toastRef.current?.show({
severity: 'error', severity: 'error',
@@ -312,12 +462,39 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
}); });
}; };
const handleOpenMetadataDialog = (jobId = null) => {
const context = jobId ? buildMetadataContextForJob(jobId) : defaultMetadataDialogContext;
if (!context?.jobId) {
showError(new Error('Kein Job mit offener Metadaten-Auswahl gefunden.'));
return;
}
setMetadataDialogContext(context);
setMetadataDialogVisible(true);
};
const handleAnalyze = async () => { const handleAnalyze = async () => {
setBusy(true); setBusy(true);
try { try {
await api.analyzeDisc(); const response = await api.analyzeDisc();
await refreshPipeline(); await refreshPipeline();
await loadDashboardJobs(); await loadDashboardJobs();
const analyzedJobId = normalizeJobId(response?.result?.jobId);
if (analyzedJobId && state === 'ENCODING') {
setMetadataDialogContext({
jobId: analyzedJobId,
detectedTitle: response?.result?.detectedTitle || '',
selectedMetadata: {
title: response?.result?.detectedTitle || '',
year: null,
imdbId: null,
poster: null
},
omdbCandidates: Array.isArray(response?.result?.omdbCandidates)
? response.result.omdbCandidates
: []
});
setMetadataDialogVisible(true);
}
} catch (error) { } catch (error) {
showError(error); showError(error);
} finally { } finally {
@@ -327,7 +504,14 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
const handleReanalyze = async () => { const handleReanalyze = async () => {
const hasActiveJob = Boolean(pipeline?.context?.jobId || pipeline?.activeJobId); const hasActiveJob = Boolean(pipeline?.context?.jobId || pipeline?.activeJobId);
if (hasActiveJob && !['IDLE', 'DISC_DETECTED', 'FINISHED'].includes(state)) { if (state === 'ENCODING') {
const confirmed = window.confirm(
'Laufendes Encoding bleibt aktiv. Neue Disk jetzt als separaten Job analysieren?'
);
if (!confirmed) {
return;
}
} else if (hasActiveJob && !['IDLE', 'DISC_DETECTED', 'FINISHED'].includes(state)) {
const confirmed = window.confirm( const confirmed = window.confirm(
'Aktuellen Ablauf verwerfen und die Disk ab der ersten MakeMKV-Analyse neu starten?' 'Aktuellen Ablauf verwerfen und die Disk ab der ersten MakeMKV-Analyse neu starten?'
); );
@@ -361,21 +545,34 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
} }
}; };
const handleCancel = async () => { const handleCancel = async (jobId = null, jobState = null) => {
const cancelledState = state; const cancelledJobId = normalizeJobId(jobId) || currentPipelineJobId;
const cancelledJobId = currentPipelineJobId;
const cancelledJob = dashboardJobs.find((item) => normalizeJobId(item?.id) === cancelledJobId) || null; const cancelledJob = dashboardJobs.find((item) => normalizeJobId(item?.id) === cancelledJobId) || null;
const cancelledState = String(
jobState
|| cancelledJob?.status
|| state
|| 'IDLE'
).trim().toUpperCase();
setBusy(true); setBusy(true);
try { try {
await api.cancelPipeline(); await api.cancelPipeline(cancelledJobId);
await refreshPipeline(); await refreshPipeline();
await loadDashboardJobs(); await loadDashboardJobs();
if (cancelledState === 'ENCODING' && cancelledJobId) { if (cancelledState === 'ENCODING' && cancelledJobId) {
setCancelCleanupDialog({ setCancelCleanupDialog({
visible: true, visible: true,
jobId: cancelledJobId, jobId: cancelledJobId,
outputPath: cancelledJob?.output_path || null target: 'movie',
path: cancelledJob?.output_path || null
});
} else if (cancelledState === 'RIPPING' && cancelledJobId) {
setCancelCleanupDialog({
visible: true,
jobId: cancelledJobId,
target: 'raw',
path: cancelledJob?.raw_path || null
}); });
} }
} catch (error) { } catch (error) {
@@ -387,24 +584,32 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
const handleDeleteCancelledOutput = async () => { const handleDeleteCancelledOutput = async () => {
const jobId = normalizeJobId(cancelCleanupDialog?.jobId); const jobId = normalizeJobId(cancelCleanupDialog?.jobId);
const target = String(cancelCleanupDialog?.target || '').trim().toLowerCase();
const effectiveTarget = target === 'raw' ? 'raw' : 'movie';
if (!jobId) { if (!jobId) {
setCancelCleanupDialog({ visible: false, jobId: null, outputPath: null }); setCancelCleanupDialog({ visible: false, jobId: null, target: null, path: null });
return; return;
} }
setCancelCleanupBusy(true); setCancelCleanupBusy(true);
try { try {
const response = await api.deleteJobFiles(jobId, 'movie'); const response = await api.deleteJobFiles(jobId, effectiveTarget);
const summary = response?.summary || {}; const summary = response?.summary || {};
const deletedFiles = effectiveTarget === 'raw'
? (summary.raw?.filesDeleted ?? 0)
: (summary.movie?.filesDeleted ?? 0);
const removedDirs = effectiveTarget === 'raw'
? (summary.raw?.dirsRemoved ?? 0)
: (summary.movie?.dirsRemoved ?? 0);
toastRef.current?.show({ toastRef.current?.show({
severity: 'success', severity: 'success',
summary: 'Movie gelöscht', summary: effectiveTarget === 'raw' ? 'RAW gelöscht' : 'Movie gelöscht',
detail: `Entfernt: ${summary.movie?.filesDeleted ?? 0} Datei(en), ${summary.movie?.dirsRemoved ?? 0} Ordner.`, detail: `Entfernt: ${deletedFiles} Datei(en), ${removedDirs} Ordner.`,
life: 4000 life: 4000
}); });
await loadDashboardJobs(); await loadDashboardJobs();
await refreshPipeline(); await refreshPipeline();
setCancelCleanupDialog({ visible: false, jobId: null, outputPath: null }); setCancelCleanupDialog({ visible: false, jobId: null, target: null, path: null });
} catch (error) { } catch (error) {
showError(error); showError(error);
} finally { } finally {
@@ -425,13 +630,19 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
await api.confirmEncodeReview(normalizedJobId, { await api.confirmEncodeReview(normalizedJobId, {
selectedEncodeTitleId: startOptions.selectedEncodeTitleId ?? null, selectedEncodeTitleId: startOptions.selectedEncodeTitleId ?? null,
selectedTrackSelection: startOptions.selectedTrackSelection ?? null, selectedTrackSelection: startOptions.selectedTrackSelection ?? null,
selectedPostEncodeScriptIds: startOptions.selectedPostEncodeScriptIds ?? [] selectedPostEncodeScriptIds: startOptions.selectedPostEncodeScriptIds ?? [],
skipPipelineStateUpdate: true
}); });
} }
await api.startJob(normalizedJobId); const response = await api.startJob(normalizedJobId);
const result = getQueueActionResult(response);
await refreshPipeline(); await refreshPipeline();
await loadDashboardJobs(); await loadDashboardJobs();
if (result.queued) {
showQueuedToast(toastRef, 'Start', result);
} else {
setExpandedJobId(normalizedJobId); setExpandedJobId(normalizedJobId);
}
} catch (error) { } catch (error) {
showError(error); showError(error);
} finally { } finally {
@@ -482,10 +693,15 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
const handleRetry = async (jobId) => { const handleRetry = async (jobId) => {
setBusy(true); setBusy(true);
try { try {
await api.retryJob(jobId); const response = await api.retryJob(jobId);
const result = getQueueActionResult(response);
await refreshPipeline(); await refreshPipeline();
await loadDashboardJobs(); await loadDashboardJobs();
if (result.queued) {
showQueuedToast(toastRef, 'Retry', result);
} else {
setExpandedJobId(normalizeJobId(jobId)); setExpandedJobId(normalizeJobId(jobId));
}
} catch (error) { } catch (error) {
showError(error); showError(error);
} finally { } finally {
@@ -508,10 +724,15 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
setBusy(true); setBusy(true);
try { try {
await api.restartEncodeWithLastSettings(jobId); const response = await api.restartEncodeWithLastSettings(jobId);
const result = getQueueActionResult(response);
await refreshPipeline(); await refreshPipeline();
await loadDashboardJobs(); await loadDashboardJobs();
if (result.queued) {
showQueuedToast(toastRef, 'Encode-Neustart', result);
} else {
setExpandedJobId(normalizeJobId(jobId)); setExpandedJobId(normalizeJobId(jobId));
}
} catch (error) { } catch (error) {
showError(error); showError(error);
} finally { } finally {
@@ -519,6 +740,99 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
} }
}; };
const handleRestartReviewFromRaw = async (jobId) => {
const normalizedJobId = normalizeJobId(jobId);
if (!normalizedJobId) {
return;
}
setBusy(true);
try {
await api.restartReviewFromRaw(normalizedJobId);
await refreshPipeline();
await loadDashboardJobs();
setExpandedJobId(normalizedJobId);
} catch (error) {
showError(error);
} finally {
setBusy(false);
}
};
const handleQueueDragEnter = (targetJobId) => {
const targetId = normalizeJobId(targetJobId);
const draggedId = normalizeJobId(draggingQueueJobId);
if (!targetId || !draggedId || targetId === draggedId || queueReorderBusy) {
return;
}
setQueueState((prev) => {
const queuedJobs = reorderQueuedItems(prev?.queuedJobs || [], draggedId, targetId);
return {
...normalizeQueue(prev),
queuedJobs,
queuedCount: queuedJobs.length
};
});
};
const handleQueueDrop = async () => {
const draggedId = normalizeJobId(draggingQueueJobId);
setDraggingQueueJobId(null);
if (!draggedId || queueReorderBusy) {
return;
}
const orderedJobIds = (Array.isArray(queueState?.queuedJobs) ? queueState.queuedJobs : [])
.map((item) => normalizeJobId(item?.jobId))
.filter(Boolean);
if (orderedJobIds.length <= 1) {
return;
}
setQueueReorderBusy(true);
try {
const response = await api.reorderPipelineQueue(orderedJobIds);
setQueueState(normalizeQueue(response?.queue));
} catch (error) {
showError(error);
try {
const latest = await api.getPipelineQueue();
setQueueState(normalizeQueue(latest?.queue));
} catch (_reloadError) {
// ignore reload failures after reorder error
}
} finally {
setQueueReorderBusy(false);
}
};
const handleRemoveQueuedJob = async (jobId) => {
const normalizedJobId = normalizeJobId(jobId);
if (!normalizedJobId || queueReorderBusy) {
return;
}
setQueueReorderBusy(true);
try {
await api.cancelPipeline(normalizedJobId);
const latest = await api.getPipelineQueue();
setQueueState(normalizeQueue(latest?.queue));
} catch (error) {
showError(error);
} finally {
setQueueReorderBusy(false);
}
};
const syncQueueFromServer = async () => {
try {
const latest = await api.getPipelineQueue();
setQueueState(normalizeQueue(latest?.queue));
} catch (_error) {
// ignore sync failures
}
};
const handleOmdbSearch = async (query) => { const handleOmdbSearch = async (query) => {
try { try {
const response = await api.searchOmdb(query); const response = await api.searchOmdb(query);
@@ -536,6 +850,7 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
await refreshPipeline(); await refreshPipeline();
await loadDashboardJobs(); await loadDashboardJobs();
setMetadataDialogVisible(false); setMetadataDialogVisible(false);
setMetadataDialogContext(null);
} catch (error) { } catch (error) {
showError(error); showError(error);
} finally { } finally {
@@ -544,13 +859,103 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
}; };
const device = lastDiscEvent || pipeline?.context?.device; const device = lastDiscEvent || pipeline?.context?.device;
const canReanalyze = !processingStates.includes(state); const canReanalyze = state === 'ENCODING'
const canOpenMetadataModal = pipeline?.state === 'METADATA_SELECTION' || pipeline?.state === 'WAITING_FOR_USER_DECISION'; ? Boolean(device)
: !processingStates.includes(state);
const canOpenMetadataModal = Boolean(defaultMetadataDialogContext?.jobId);
const queueRunningJobs = Array.isArray(queueState?.runningJobs) ? queueState.runningJobs : [];
const queuedJobs = Array.isArray(queueState?.queuedJobs) ? queueState.queuedJobs : [];
const canReorderQueue = queuedJobs.length > 1 && !queueReorderBusy;
const queuedJobIdSet = useMemo(() => {
const set = new Set();
for (const item of queuedJobs) {
const id = normalizeJobId(item?.jobId);
if (id) {
set.add(id);
}
}
return set;
}, [queuedJobs]);
return ( return (
<div className="page-grid"> <div className="page-grid">
<Toast ref={toastRef} /> <Toast ref={toastRef} />
<Card title="Job Queue" subTitle="Starts werden nach Parallel-Limit abgearbeitet. Queue-Elemente können per Drag-and-Drop umsortiert werden.">
<div className="pipeline-queue-meta">
<Tag value={`Parallel: ${queueState?.maxParallelJobs || 1}`} severity="info" />
<Tag value={`Laufend: ${queueState?.runningCount || 0}`} severity={queueRunningJobs.length > 0 ? 'warning' : 'success'} />
<Tag value={`Wartend: ${queueState?.queuedCount || 0}`} severity={queuedJobs.length > 0 ? 'warning' : 'success'} />
</div>
<div className="pipeline-queue-grid">
<div className="pipeline-queue-col">
<h4>Laufende Jobs</h4>
{queueRunningJobs.length === 0 ? (
<small>Keine laufenden Jobs.</small>
) : (
queueRunningJobs.map((item) => (
<div key={`running-${item.jobId}`} className="pipeline-queue-item running">
<strong>#{item.jobId} | {item.title || `Job #${item.jobId}`}</strong>
<small>{getStatusLabel(item.status)}</small>
</div>
))
)}
</div>
<div className="pipeline-queue-col">
<h4>Warteschlange</h4>
{queuedJobs.length === 0 ? (
<small>Queue ist leer.</small>
) : (
queuedJobs.map((item) => {
const queuedJobId = normalizeJobId(item?.jobId);
const isDragging = normalizeJobId(draggingQueueJobId) === queuedJobId;
return (
<div
key={`queued-${item.jobId}`}
className={`pipeline-queue-item queued${isDragging ? ' dragging' : ''}`}
draggable={canReorderQueue}
onDragStart={() => setDraggingQueueJobId(queuedJobId)}
onDragEnter={() => handleQueueDragEnter(queuedJobId)}
onDragOver={(event) => event.preventDefault()}
onDrop={(event) => {
event.preventDefault();
void handleQueueDrop();
}}
onDragEnd={() => {
setDraggingQueueJobId(null);
void syncQueueFromServer();
}}
>
<span className={`pipeline-queue-drag-handle${canReorderQueue ? '' : ' disabled'}`} title="Reihenfolge ändern">
<i className="pi pi-bars" />
</span>
<div className="pipeline-queue-item-main">
<strong>{item.position || '-'} | #{item.jobId} | {item.title || `Job #${item.jobId}`}</strong>
<small>{item.actionLabel || item.action || '-'} | Status {getStatusLabel(item.status)}</small>
</div>
<Button
icon="pi pi-times"
severity="danger"
text
rounded
size="small"
className="pipeline-queue-remove-btn"
disabled={queueReorderBusy}
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
void handleRemoveQueuedJob(queuedJobId);
}}
/>
</div>
);
})
)}
</div>
</div>
</Card>
<Card title="Job Übersicht" subTitle="Kompakte Liste; Klick auf Zeile öffnet die volle Job-Detailansicht mit passenden CTAs"> <Card title="Job Übersicht" subTitle="Kompakte Liste; Klick auf Zeile öffnet die volle Job-Detailansicht mit passenden CTAs">
{jobsLoading ? ( {jobsLoading ? (
<p>Jobs werden geladen ...</p> <p>Jobs werden geladen ...</p>
@@ -563,9 +968,13 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
if (!jobId) { if (!jobId) {
return null; return null;
} }
const normalizedStatus = normalizeStatus(job?.status);
const isQueued = queuedJobIdSet.has(jobId);
const statusBadgeValue = getStatusLabel(job?.status, { queued: isQueued });
const statusBadgeSeverity = getStatusSeverity(normalizedStatus, { queued: isQueued });
const isExpanded = normalizeJobId(expandedJobId) === jobId; const isExpanded = normalizeJobId(expandedJobId) === jobId;
const isCurrentSession = currentPipelineJobId === jobId && state !== 'IDLE'; const isCurrentSession = currentPipelineJobId === jobId && state !== 'IDLE';
const isResumable = String(job?.status || '').trim().toUpperCase() === 'READY_TO_ENCODE' && !isCurrentSession; const isResumable = normalizedStatus === 'READY_TO_ENCODE' && !isCurrentSession;
const reviewConfirmed = Boolean(Number(job?.encode_review_confirmed || 0)); const reviewConfirmed = Boolean(Number(job?.encode_review_confirmed || 0));
const pipelineForJob = pipelineByJobId.get(jobId) || pipeline; const pipelineForJob = pipelineByJobId.get(jobId) || pipeline;
const jobTitle = job?.title || job?.detected_title || `Job #${jobId}`; const jobTitle = job?.title || job?.detected_title || `Job #${jobId}`;
@@ -592,10 +1001,10 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
<span>#{jobId} | {jobTitle}</span> <span>#{jobId} | {jobTitle}</span>
</strong> </strong>
<div className="dashboard-job-badges"> <div className="dashboard-job-badges">
<Tag value={String(job?.status || '-')} severity={statusSeverityMap[String(job?.status || '').trim().toUpperCase()] || 'secondary'} /> <Tag value={statusBadgeValue} severity={statusBadgeSeverity} />
{isCurrentSession ? <Tag value="Aktive Session" severity="info" /> : null} {isCurrentSession ? <Tag value="Aktive Session" severity="info" /> : null}
{isResumable ? <Tag value="Fortsetzbar" severity="success" /> : null} {isResumable ? <Tag value="Fortsetzbar" severity="success" /> : null}
{String(job?.status || '').trim().toUpperCase() === 'READY_TO_ENCODE' {normalizedStatus === 'READY_TO_ENCODE'
? <Tag value={reviewConfirmed ? 'Review bestätigt' : 'Review offen'} severity={reviewConfirmed ? 'success' : 'warning'} /> ? <Tag value={reviewConfirmed ? 'Review bestätigt' : 'Review offen'} severity={reviewConfirmed ? 'success' : 'warning'} />
: null} : null}
<JobStepChecks backupSuccess={Boolean(job?.backupSuccess)} encodeSuccess={Boolean(job?.encodeSuccess)} /> <JobStepChecks backupSuccess={Boolean(job?.backupSuccess)} encodeSuccess={Boolean(job?.encodeSuccess)} />
@@ -614,12 +1023,16 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
pipeline={pipelineForJob} pipeline={pipelineForJob}
onAnalyze={handleAnalyze} onAnalyze={handleAnalyze}
onReanalyze={handleReanalyze} onReanalyze={handleReanalyze}
onOpenMetadata={handleOpenMetadataDialog}
onStart={handleStartJob} onStart={handleStartJob}
onRestartEncode={handleRestartEncodeWithLastSettings} onRestartEncode={handleRestartEncodeWithLastSettings}
onRestartReview={handleRestartReviewFromRaw}
onConfirmReview={handleConfirmReview} onConfirmReview={handleConfirmReview}
onSelectPlaylist={handleSelectPlaylist} onSelectPlaylist={handleSelectPlaylist}
onCancel={handleCancel} onCancel={handleCancel}
onRetry={handleRetry} onRetry={handleRetry}
onRemoveFromQueue={handleRemoveQueuedJob}
isQueued={isQueued}
busy={busy} busy={busy}
liveJobLog={isCurrentSession ? liveJobLog : ''} liveJobLog={isCurrentSession ? liveJobLog : ''}
/> />
@@ -660,10 +1073,10 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
</div> </div>
</div> </div>
<div className="dashboard-job-badges"> <div className="dashboard-job-badges">
<Tag value={String(job?.status || '-')} severity={statusSeverityMap[String(job?.status || '').trim().toUpperCase()] || 'secondary'} /> <Tag value={statusBadgeValue} severity={statusBadgeSeverity} />
{isCurrentSession ? <Tag value="Aktive Session" severity="info" /> : null} {isCurrentSession ? <Tag value="Aktive Session" severity="info" /> : null}
{isResumable ? <Tag value="Fortsetzbar" severity="success" /> : null} {isResumable ? <Tag value="Fortsetzbar" severity="success" /> : null}
{String(job?.status || '').trim().toUpperCase() === 'READY_TO_ENCODE' {normalizedStatus === 'READY_TO_ENCODE'
? <Tag value={reviewConfirmed ? 'Bestätigt' : 'Unbestätigt'} severity={reviewConfirmed ? 'success' : 'warning'} /> ? <Tag value={reviewConfirmed ? 'Bestätigt' : 'Unbestätigt'} severity={reviewConfirmed ? 'success' : 'warning'} />
: null} : null}
<JobStepChecks backupSuccess={Boolean(job?.backupSuccess)} encodeSuccess={Boolean(job?.encodeSuccess)} /> <JobStepChecks backupSuccess={Boolean(job?.backupSuccess)} encodeSuccess={Boolean(job?.encodeSuccess)} />
@@ -696,7 +1109,7 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
<Button <Button
label="Metadaten-Modal öffnen" label="Metadaten-Modal öffnen"
icon="pi pi-list" icon="pi pi-list"
onClick={() => setMetadataDialogVisible(true)} onClick={() => handleOpenMetadataDialog()}
disabled={!canOpenMetadataModal} disabled={!canOpenMetadataModal}
/> />
</div> </div>
@@ -725,36 +1138,43 @@ export default function DashboardPage({ pipeline, lastDiscEvent, refreshPipeline
<MetadataSelectionDialog <MetadataSelectionDialog
visible={metadataDialogVisible} visible={metadataDialogVisible}
context={pipeline?.context || {}} context={effectiveMetadataDialogContext}
onHide={() => setMetadataDialogVisible(false)} onHide={() => {
setMetadataDialogVisible(false);
setMetadataDialogContext(null);
}}
onSubmit={handleMetadataSubmit} onSubmit={handleMetadataSubmit}
onSearch={handleOmdbSearch} onSearch={handleOmdbSearch}
busy={busy} busy={busy}
/> />
<Dialog <Dialog
header="Encode abgebrochen" header={cancelCleanupDialog?.target === 'raw' ? 'Rip abgebrochen' : 'Encode abgebrochen'}
visible={Boolean(cancelCleanupDialog.visible)} visible={Boolean(cancelCleanupDialog.visible)}
onHide={() => setCancelCleanupDialog({ visible: false, jobId: null, outputPath: null })} onHide={() => setCancelCleanupDialog({ visible: false, jobId: null, target: null, path: null })}
style={{ width: '32rem', maxWidth: '96vw' }} style={{ width: '32rem', maxWidth: '96vw' }}
modal modal
> >
<p> <p>
Soll die bisher erzeugte Movie-Datei inklusive Job-Ordner im Ausgabeverzeichnis gelöscht werden? {cancelCleanupDialog?.target === 'raw'
? 'Soll der bisher erzeugte RAW-Ordner gelöscht werden?'
: 'Soll die bisher erzeugte Movie-Datei inklusive Job-Ordner im Ausgabeverzeichnis gelöscht werden?'}
</p> </p>
{cancelCleanupDialog?.outputPath ? ( {cancelCleanupDialog?.path ? (
<small className="muted-inline">Output-Pfad: {cancelCleanupDialog.outputPath}</small> <small className="muted-inline">
{cancelCleanupDialog?.target === 'raw' ? 'RAW-Pfad' : 'Output-Pfad'}: {cancelCleanupDialog.path}
</small>
) : null} ) : null}
<div className="dialog-actions"> <div className="dialog-actions">
<Button <Button
label="Behalten" label="Behalten"
severity="secondary" severity="secondary"
outlined outlined
onClick={() => setCancelCleanupDialog({ visible: false, jobId: null, outputPath: null })} onClick={() => setCancelCleanupDialog({ visible: false, jobId: null, target: null, path: null })}
disabled={cancelCleanupBusy} disabled={cancelCleanupBusy}
/> />
<Button <Button
label="Movie löschen" label={cancelCleanupDialog?.target === 'raw' ? 'RAW löschen' : 'Movie löschen'}
icon="pi pi-trash" icon="pi pi-trash"
severity="danger" severity="danger"
onClick={handleDeleteCancelledOutput} onClick={handleDeleteCancelledOutput}

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { Card } from 'primereact/card'; import { Card } from 'primereact/card';
import { DataTable } from 'primereact/datatable'; import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column'; import { Column } from 'primereact/column';
@@ -12,35 +12,30 @@ import JobDetailDialog from '../components/JobDetailDialog';
import MetadataSelectionDialog from '../components/MetadataSelectionDialog'; import MetadataSelectionDialog from '../components/MetadataSelectionDialog';
import blurayIndicatorIcon from '../assets/media-bluray.svg'; import blurayIndicatorIcon from '../assets/media-bluray.svg';
import discIndicatorIcon from '../assets/media-disc.svg'; import discIndicatorIcon from '../assets/media-disc.svg';
import {
const statusOptions = [ getStatusLabel,
{ label: 'Alle', value: '' }, getStatusSeverity,
{ label: 'FINISHED', value: 'FINISHED' }, normalizeStatus,
{ label: 'ERROR', value: 'ERROR' }, STATUS_FILTER_OPTIONS
{ label: 'WAITING_FOR_USER_DECISION', value: 'WAITING_FOR_USER_DECISION' }, } from '../utils/statusPresentation';
{ label: 'READY_TO_START', value: 'READY_TO_START' },
{ label: 'READY_TO_ENCODE', value: 'READY_TO_ENCODE' },
{ label: 'MEDIAINFO_CHECK', value: 'MEDIAINFO_CHECK' },
{ label: 'RIPPING', value: 'RIPPING' },
{ label: 'ENCODING', value: 'ENCODING' },
{ label: 'ANALYZING', value: 'ANALYZING' },
{ label: 'METADATA_SELECTION', value: 'METADATA_SELECTION' }
];
function statusSeverity(status) {
if (status === 'FINISHED') return 'success';
if (status === 'ERROR') return 'danger';
if (status === 'READY_TO_START' || status === 'READY_TO_ENCODE') return 'info';
if (status === 'WAITING_FOR_USER_DECISION') return 'warning';
if (status === 'RIPPING' || status === 'ENCODING' || status === 'ANALYZING' || status === 'MEDIAINFO_CHECK') return 'warning';
return 'secondary';
}
function resolveMediaType(row) { function resolveMediaType(row) {
const raw = String(row?.mediaType || row?.media_type || '').trim().toLowerCase(); const raw = String(row?.mediaType || row?.media_type || '').trim().toLowerCase();
return raw === 'bluray' ? 'bluray' : 'disc'; return raw === 'bluray' ? 'bluray' : 'disc';
} }
function normalizeJobId(value) {
const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed <= 0) {
return null;
}
return Math.trunc(parsed);
}
function getQueueActionResult(response) {
return response?.result && typeof response.result === 'object' ? response.result : {};
}
export default function DatabasePage() { export default function DatabasePage() {
const [rows, setRows] = useState([]); const [rows, setRows] = useState([]);
const [orphanRows, setOrphanRows] = useState([]); const [orphanRows, setOrphanRows] = useState([]);
@@ -59,7 +54,18 @@ export default function DatabasePage() {
const [reencodeBusyJobId, setReencodeBusyJobId] = useState(null); const [reencodeBusyJobId, setReencodeBusyJobId] = useState(null);
const [deleteEntryBusyJobId, setDeleteEntryBusyJobId] = useState(null); const [deleteEntryBusyJobId, setDeleteEntryBusyJobId] = useState(null);
const [orphanImportBusyPath, setOrphanImportBusyPath] = useState(null); const [orphanImportBusyPath, setOrphanImportBusyPath] = useState(null);
const [queuedJobIds, setQueuedJobIds] = useState([]);
const toastRef = useRef(null); const toastRef = useRef(null);
const queuedJobIdSet = useMemo(() => {
const next = new Set();
for (const value of Array.isArray(queuedJobIds) ? queuedJobIds : []) {
const id = normalizeJobId(value);
if (id) {
next.add(id);
}
}
return next;
}, [queuedJobIds]);
const loadRows = async () => { const loadRows = async () => {
setLoading(true); setLoading(true);
@@ -85,8 +91,21 @@ export default function DatabasePage() {
} }
}; };
const loadQueue = async () => {
try {
const response = await api.getPipelineQueue();
const queuedRows = Array.isArray(response?.queue?.queuedJobs) ? response.queue.queuedJobs : [];
const queuedIds = queuedRows
.map((item) => normalizeJobId(item?.jobId))
.filter(Boolean);
setQueuedJobIds(queuedIds);
} catch (_error) {
setQueuedJobIds([]);
}
};
const load = async () => { const load = async () => {
await Promise.all([loadRows(), loadOrphans()]); await Promise.all([loadRows(), loadOrphans(), loadQueue()]);
}; };
useEffect(() => { useEffect(() => {
@@ -241,6 +260,116 @@ export default function DatabasePage() {
} }
}; };
const handleRestartEncode = async (row) => {
const title = row.title || row.detected_title || `Job #${row.id}`;
if (row?.encodeSuccess) {
const confirmed = window.confirm(
`Encode für "${title}" ist bereits erfolgreich abgeschlossen. Wirklich erneut encodieren?\n` +
'Es wird eine neue Datei mit Kollisionsprüfung angelegt.'
);
if (!confirmed) {
return;
}
}
setActionBusy(true);
try {
const response = await api.restartEncodeWithLastSettings(row.id);
const result = getQueueActionResult(response);
if (result.queued) {
const queuePosition = Number(result?.queuePosition || 0);
toastRef.current?.show({
severity: 'info',
summary: 'Encode-Neustart in Queue',
detail: queuePosition > 0
? `Job wurde auf Position ${queuePosition} eingeplant.`
: 'Job wurde in die Warteschlange eingeplant.',
life: 3500
});
} else {
toastRef.current?.show({
severity: 'success',
summary: 'Encode-Neustart gestartet',
detail: 'Letzte bestätigte Einstellungen werden verwendet.',
life: 3500
});
}
await load();
await refreshDetailIfOpen(row.id);
} catch (error) {
toastRef.current?.show({ severity: 'error', summary: 'Encode-Neustart fehlgeschlagen', detail: error.message, life: 4500 });
} finally {
setActionBusy(false);
}
};
const handleRestartReview = async (row) => {
setActionBusy(true);
try {
await api.restartReviewFromRaw(row.id);
toastRef.current?.show({
severity: 'success',
summary: 'Review-Neustart gestartet',
detail: 'Die Titel-/Spurprüfung wird aus dem RAW neu berechnet.',
life: 3500
});
await load();
await refreshDetailIfOpen(row.id);
} catch (error) {
toastRef.current?.show({ severity: 'error', summary: 'Review-Neustart fehlgeschlagen', detail: error.message, life: 4500 });
} finally {
setActionBusy(false);
}
};
const handleRemoveFromQueue = async (row) => {
const jobId = normalizeJobId(row?.id || row);
if (!jobId) {
return;
}
setActionBusy(true);
try {
await api.cancelPipeline(jobId);
toastRef.current?.show({
severity: 'success',
summary: 'Aus Queue entfernt',
detail: `Job #${jobId} wurde aus der Warteschlange entfernt.`,
life: 3200
});
await load();
await refreshDetailIfOpen(jobId);
} catch (error) {
toastRef.current?.show({
severity: 'error',
summary: 'Queue-Entfernung fehlgeschlagen',
detail: error.message,
life: 4500
});
} finally {
setActionBusy(false);
}
};
const handleResumeReady = async (row) => {
setActionBusy(true);
try {
await api.resumeReadyJob(row.id);
toastRef.current?.show({
severity: 'success',
summary: 'Job ins Dashboard geladen',
detail: 'Job ist wieder im Dashboard aktiv.',
life: 3200
});
await load();
await refreshDetailIfOpen(row.id);
} catch (error) {
toastRef.current?.show({ severity: 'error', summary: 'Laden fehlgeschlagen', detail: error.message, life: 4500 });
} finally {
setActionBusy(false);
}
};
const mapDeleteChoice = (value) => { const mapDeleteChoice = (value) => {
const normalized = String(value || '').trim().toLowerCase(); const normalized = String(value || '').trim().toLowerCase();
if (normalized === 'raw') return 'raw'; if (normalized === 'raw') return 'raw';
@@ -431,7 +560,17 @@ export default function DatabasePage() {
</div> </div>
); );
const stateBody = (row) => <Tag value={row.status} severity={statusSeverity(row.status)} />; const stateBody = (row) => {
const normalizedStatus = normalizeStatus(row?.status);
const rowId = normalizeJobId(row?.id);
const isQueued = Boolean(rowId && queuedJobIdSet.has(rowId));
return (
<Tag
value={getStatusLabel(row?.status, { queued: isQueued })}
severity={getStatusSeverity(normalizedStatus, { queued: isQueued })}
/>
);
};
const mediaBody = (row) => { const mediaBody = (row) => {
const mediaType = resolveMediaType(row); const mediaType = resolveMediaType(row);
const src = mediaType === 'bluray' ? blurayIndicatorIcon : discIndicatorIcon; const src = mediaType === 'bluray' ? blurayIndicatorIcon : discIndicatorIcon;
@@ -480,7 +619,7 @@ export default function DatabasePage() {
/> />
<Dropdown <Dropdown
value={status} value={status}
options={statusOptions} options={STATUS_FILTER_OPTIONS}
optionLabel="label" optionLabel="label"
optionValue="value" optionValue="value"
onChange={(event) => setStatus(event.value)} onChange={(event) => setStatus(event.value)}
@@ -556,9 +695,14 @@ export default function DatabasePage() {
setLogLoadingMode(null); setLogLoadingMode(null);
}} }}
onAssignOmdb={openMetadataAssignDialog} onAssignOmdb={openMetadataAssignDialog}
onResumeReady={handleResumeReady}
onRestartEncode={handleRestartEncode}
onRestartReview={handleRestartReview}
onReencode={handleReencode} onReencode={handleReencode}
onDeleteFiles={handleDeleteFiles} onDeleteFiles={handleDeleteFiles}
onDeleteEntry={handleDeleteEntry} onDeleteEntry={handleDeleteEntry}
onRemoveFromQueue={handleRemoveFromQueue}
isQueued={Boolean(selectedJob?.id && queuedJobIdSet.has(normalizeJobId(selectedJob.id)))}
omdbAssignBusy={metadataDialogBusy} omdbAssignBusy={metadataDialogBusy}
actionBusy={actionBusy} actionBusy={actionBusy}
reencodeBusy={reencodeBusyJobId === selectedJob?.id} reencodeBusy={reencodeBusyJobId === selectedJob?.id}

View File

@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { Card } from 'primereact/card'; import { Card } from 'primereact/card';
import { DataTable } from 'primereact/datatable'; import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column'; import { Column } from 'primereact/column';
@@ -11,26 +11,31 @@ import { api } from '../api/client';
import JobDetailDialog from '../components/JobDetailDialog'; import JobDetailDialog from '../components/JobDetailDialog';
import blurayIndicatorIcon from '../assets/media-bluray.svg'; import blurayIndicatorIcon from '../assets/media-bluray.svg';
import discIndicatorIcon from '../assets/media-disc.svg'; import discIndicatorIcon from '../assets/media-disc.svg';
import {
const statusOptions = [ getStatusLabel,
{ label: 'Alle', value: '' }, getStatusSeverity,
{ label: 'FINISHED', value: 'FINISHED' }, getProcessStatusLabel,
{ label: 'ERROR', value: 'ERROR' }, normalizeStatus,
{ label: 'WAITING_FOR_USER_DECISION', value: 'WAITING_FOR_USER_DECISION' }, STATUS_FILTER_OPTIONS
{ label: 'READY_TO_START', value: 'READY_TO_START' }, } from '../utils/statusPresentation';
{ label: 'READY_TO_ENCODE', value: 'READY_TO_ENCODE' },
{ label: 'MEDIAINFO_CHECK', value: 'MEDIAINFO_CHECK' },
{ label: 'RIPPING', value: 'RIPPING' },
{ label: 'ENCODING', value: 'ENCODING' },
{ label: 'ANALYZING', value: 'ANALYZING' },
{ label: 'METADATA_SELECTION', value: 'METADATA_SELECTION' }
];
function resolveMediaType(row) { function resolveMediaType(row) {
const raw = String(row?.mediaType || row?.media_type || '').trim().toLowerCase(); const raw = String(row?.mediaType || row?.media_type || '').trim().toLowerCase();
return raw === 'bluray' ? 'bluray' : 'disc'; return raw === 'bluray' ? 'bluray' : 'disc';
} }
function normalizeJobId(value) {
const parsed = Number(value);
if (!Number.isFinite(parsed) || parsed <= 0) {
return null;
}
return Math.trunc(parsed);
}
function getQueueActionResult(response) {
return response?.result && typeof response.result === 'object' ? response.result : {};
}
export default function HistoryPage() { export default function HistoryPage() {
const [jobs, setJobs] = useState([]); const [jobs, setJobs] = useState([]);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
@@ -42,13 +47,40 @@ export default function HistoryPage() {
const [actionBusy, setActionBusy] = useState(false); const [actionBusy, setActionBusy] = useState(false);
const [reencodeBusyJobId, setReencodeBusyJobId] = useState(null); const [reencodeBusyJobId, setReencodeBusyJobId] = useState(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [queuedJobIds, setQueuedJobIds] = useState([]);
const toastRef = useRef(null); const toastRef = useRef(null);
const queuedJobIdSet = useMemo(() => {
const next = new Set();
for (const value of Array.isArray(queuedJobIds) ? queuedJobIds : []) {
const id = normalizeJobId(value);
if (id) {
next.add(id);
}
}
return next;
}, [queuedJobIds]);
const load = async () => { const load = async () => {
setLoading(true); setLoading(true);
try { try {
const response = await api.getJobs({ search, status }); const [jobsResponse, queueResponse] = await Promise.allSettled([
setJobs(response.jobs || []); api.getJobs({ search, status }),
api.getPipelineQueue()
]);
if (jobsResponse.status === 'fulfilled') {
setJobs(jobsResponse.value.jobs || []);
} else {
setJobs([]);
}
if (queueResponse.status === 'fulfilled') {
const queuedRows = Array.isArray(queueResponse.value?.queue?.queuedJobs) ? queueResponse.value.queue.queuedJobs : [];
const queuedIds = queuedRows
.map((item) => normalizeJobId(item?.jobId))
.filter(Boolean);
setQueuedJobIds(queuedIds);
} else {
setQueuedJobIds([]);
}
} catch (error) { } catch (error) {
toastRef.current?.show({ severity: 'error', summary: 'Fehler', detail: error.message }); toastRef.current?.show({ severity: 'error', summary: 'Fehler', detail: error.message });
} finally { } finally {
@@ -189,13 +221,26 @@ export default function HistoryPage() {
setActionBusy(true); setActionBusy(true);
try { try {
await api.restartEncodeWithLastSettings(row.id); const response = await api.restartEncodeWithLastSettings(row.id);
const result = getQueueActionResult(response);
if (result.queued) {
const queuePosition = Number(result?.queuePosition || 0);
toastRef.current?.show({
severity: 'info',
summary: 'Encode-Neustart in Queue',
detail: queuePosition > 0
? `Job wurde auf Position ${queuePosition} eingeplant.`
: 'Job wurde in die Warteschlange eingeplant.',
life: 3500
});
} else {
toastRef.current?.show({ toastRef.current?.show({
severity: 'success', severity: 'success',
summary: 'Encode-Neustart gestartet', summary: 'Encode-Neustart gestartet',
detail: 'Letzte bestätigte Einstellungen werden verwendet.', detail: 'Letzte bestätigte Einstellungen werden verwendet.',
life: 3500 life: 3500
}); });
}
await load(); await load();
await refreshDetailIfOpen(row.id); await refreshDetailIfOpen(row.id);
} catch (error) { } catch (error) {
@@ -205,17 +250,64 @@ export default function HistoryPage() {
} }
}; };
const statusBody = (row) => <Tag value={row.status} />; const handleRemoveFromQueue = async (row) => {
const jobId = normalizeJobId(row?.id || row);
if (!jobId) {
return;
}
setActionBusy(true);
try {
await api.cancelPipeline(jobId);
toastRef.current?.show({
severity: 'success',
summary: 'Aus Queue entfernt',
detail: `Job #${jobId} wurde aus der Warteschlange entfernt.`,
life: 3200
});
await load();
await refreshDetailIfOpen(jobId);
} catch (error) {
toastRef.current?.show({
severity: 'error',
summary: 'Queue-Entfernung fehlgeschlagen',
detail: error.message,
life: 4500
});
} finally {
setActionBusy(false);
}
};
const statusBody = (row) => {
const normalizedStatus = normalizeStatus(row?.status);
const rowId = normalizeJobId(row?.id);
const isQueued = Boolean(rowId && queuedJobIdSet.has(rowId));
return (
<Tag
value={getStatusLabel(row?.status, { queued: isQueued })}
severity={getStatusSeverity(normalizedStatus, { queued: isQueued })}
/>
);
};
const mkBody = (row) => ( const mkBody = (row) => (
<span className="job-step-cell"> <span className="job-step-cell">
{row?.backupSuccess ? <i className="pi pi-check-circle job-step-ok-icon" aria-label="Backup erfolgreich" title="Backup erfolgreich" /> : null} {row?.backupSuccess ? <i className="pi pi-check-circle job-step-ok-icon" aria-label="Backup erfolgreich" title="Backup erfolgreich" /> : null}
<span>{row.makemkvInfo ? `${row.makemkvInfo.status || '-'} ${typeof row.makemkvInfo.lastProgress === 'number' ? `${row.makemkvInfo.lastProgress.toFixed(1)}%` : ''}` : '-'}</span> <span>
{row.makemkvInfo
? `${getProcessStatusLabel(row.makemkvInfo.status)} ${typeof row.makemkvInfo.lastProgress === 'number' ? `${row.makemkvInfo.lastProgress.toFixed(1)}%` : ''}`
: '-'}
</span>
</span> </span>
); );
const hbBody = (row) => ( const hbBody = (row) => (
<span className="job-step-cell"> <span className="job-step-cell">
{row?.encodeSuccess ? <i className="pi pi-check-circle job-step-ok-icon" aria-label="Encode erfolgreich" title="Encode erfolgreich" /> : null} {row?.encodeSuccess ? <i className="pi pi-check-circle job-step-ok-icon" aria-label="Encode erfolgreich" title="Encode erfolgreich" /> : null}
<span>{row.handbrakeInfo ? `${row.handbrakeInfo.status || '-'} ${typeof row.handbrakeInfo.lastProgress === 'number' ? `${row.handbrakeInfo.lastProgress.toFixed(1)}%` : ''}` : '-'}</span> <span>
{row.handbrakeInfo
? `${getProcessStatusLabel(row.handbrakeInfo.status)} ${typeof row.handbrakeInfo.lastProgress === 'number' ? `${row.handbrakeInfo.lastProgress.toFixed(1)}%` : ''}`
: '-'}
</span>
</span> </span>
); );
const mediaBody = (row) => { const mediaBody = (row) => {
@@ -245,7 +337,7 @@ export default function HistoryPage() {
/> />
<Dropdown <Dropdown
value={status} value={status}
options={statusOptions} options={STATUS_FILTER_OPTIONS}
optionLabel="label" optionLabel="label"
optionValue="value" optionValue="value"
onChange={(event) => setStatus(event.value)} onChange={(event) => setStatus(event.value)}
@@ -291,6 +383,8 @@ export default function HistoryPage() {
onRestartEncode={handleRestartEncode} onRestartEncode={handleRestartEncode}
onReencode={handleReencode} onReencode={handleReencode}
onDeleteFiles={handleDeleteFiles} onDeleteFiles={handleDeleteFiles}
onRemoveFromQueue={handleRemoveFromQueue}
isQueued={Boolean(selectedJob?.id && queuedJobIdSet.has(normalizeJobId(selectedJob.id)))}
actionBusy={actionBusy} actionBusy={actionBusy}
reencodeBusy={reencodeBusyJobId === selectedJob?.id} reencodeBusy={reencodeBusyJobId === selectedJob?.id}
onHide={() => { onHide={() => {

View File

@@ -667,7 +667,7 @@ export default function SettingsPage() {
<div className="script-test-result"> <div className="script-test-result">
<h4>Letzter Script-Test: {lastScriptTestResult.scriptName}</h4> <h4>Letzter Script-Test: {lastScriptTestResult.scriptName}</h4>
<small> <small>
Status: {lastScriptTestResult.success ? 'SUCCESS' : 'ERROR'} Status: {lastScriptTestResult.success ? 'Erfolgreich' : 'Fehler'}
{' | '}exit={lastScriptTestResult.exitCode ?? 'n/a'} {' | '}exit={lastScriptTestResult.exitCode ?? 'n/a'}
{' | '}timeout={lastScriptTestResult.timedOut ? 'ja' : 'nein'} {' | '}timeout={lastScriptTestResult.timedOut ? 'ja' : 'nein'}
{' | '}dauer={Number(lastScriptTestResult.durationMs || 0)}ms {' | '}dauer={Number(lastScriptTestResult.durationMs || 0)}ms

View File

@@ -217,6 +217,89 @@ body {
flex-wrap: wrap; flex-wrap: wrap;
} }
.pipeline-queue-meta {
display: flex;
gap: 0.45rem;
flex-wrap: wrap;
margin-bottom: 0.7rem;
}
.pipeline-queue-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.75rem;
}
.pipeline-queue-col {
border: 1px solid var(--rip-border);
border-radius: 0.5rem;
padding: 0.55rem 0.6rem;
background: var(--rip-panel-soft);
display: grid;
align-content: start;
grid-auto-rows: min-content;
gap: 0.45rem;
}
.pipeline-queue-col h4 {
margin: 0;
}
.pipeline-queue-item {
border: 1px dashed var(--rip-border);
border-radius: 0.45rem;
padding: 0.42rem 0.5rem;
background: var(--rip-panel);
display: grid;
gap: 0.15rem;
}
.pipeline-queue-item.running {
border-style: solid;
}
.pipeline-queue-item.queued {
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: 0.45rem;
}
.pipeline-queue-item.queued.dragging {
opacity: 0.65;
}
.pipeline-queue-item-main {
min-width: 0;
display: grid;
gap: 0.15rem;
}
.pipeline-queue-item-main strong,
.pipeline-queue-item-main small {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.pipeline-queue-drag-handle {
cursor: grab;
color: var(--rip-muted);
font-size: 0.9rem;
border: 1px solid transparent;
border-radius: 0.3rem;
padding: 0.2rem;
}
.pipeline-queue-drag-handle.disabled {
opacity: 0.45;
cursor: not-allowed;
}
.pipeline-queue-remove-btn {
width: 1.9rem;
height: 1.9rem;
}
.dashboard-job-list { .dashboard-job-list {
display: grid; display: grid;
gap: 0.6rem; gap: 0.6rem;
@@ -1206,6 +1289,7 @@ body {
.metadata-grid, .metadata-grid,
.device-meta, .device-meta,
.pipeline-queue-grid,
.media-review-meta, .media-review-meta,
.media-track-grid, .media-track-grid,
.job-meta-grid, .job-meta-grid,
@@ -1309,6 +1393,11 @@ body {
white-space: normal; white-space: normal;
} }
.pipeline-queue-item-main strong,
.pipeline-queue-item-main small {
white-space: normal;
}
.dashboard-job-title-line > span { .dashboard-job-title-line > span {
white-space: normal; white-space: normal;
} }

View File

@@ -0,0 +1,80 @@
const STATUS_LABELS = {
IDLE: 'Bereit',
DISC_DETECTED: 'Medium erkannt',
ANALYZING: 'Analyse',
METADATA_SELECTION: 'Metadatenauswahl',
WAITING_FOR_USER_DECISION: 'Warte auf Auswahl',
READY_TO_START: 'Startbereit',
MEDIAINFO_CHECK: 'Mediainfo-Pruefung',
READY_TO_ENCODE: 'Bereit zum Encodieren',
RIPPING: 'Rippen',
ENCODING: 'Encodieren',
POST_ENCODE_SCRIPTS: 'Nachbearbeitung',
FINISHED: 'Fertig',
CANCELLED: 'Abgebrochen',
ERROR: 'Fehler'
};
const PROCESS_STATUS_LABELS = {
SUCCESS: 'Erfolgreich',
ERROR: 'Fehler',
CANCELLED: 'Abgebrochen',
RUNNING: 'Laeuft',
STARTED: 'Gestartet',
PENDING: 'Ausstehend'
};
export function normalizeStatus(status) {
return String(status || '').trim().toUpperCase();
}
export function getStatusLabel(status, options = {}) {
if (options?.queued) {
return 'In der Queue';
}
const normalized = normalizeStatus(status);
return STATUS_LABELS[normalized] || (String(status || '').trim() || '-');
}
export function getStatusSeverity(status, options = {}) {
if (options?.queued) {
return 'info';
}
const normalized = normalizeStatus(status);
if (normalized === 'FINISHED') return 'success';
if (normalized === 'CANCELLED') return 'warning';
if (normalized === 'ERROR') return 'danger';
if (normalized === 'READY_TO_START' || normalized === 'READY_TO_ENCODE') return 'info';
if (normalized === 'WAITING_FOR_USER_DECISION') return 'warning';
if (
normalized === 'RIPPING'
|| normalized === 'ENCODING'
|| normalized === 'ANALYZING'
|| normalized === 'MEDIAINFO_CHECK'
|| normalized === 'METADATA_SELECTION'
|| normalized === 'POST_ENCODE_SCRIPTS'
) {
return 'warning';
}
return 'secondary';
}
export function getProcessStatusLabel(status) {
const normalized = normalizeStatus(status);
return PROCESS_STATUS_LABELS[normalized] || (String(status || '').trim() || '-');
}
export const STATUS_FILTER_OPTIONS = [
{ label: 'Alle', value: '' },
{ label: getStatusLabel('FINISHED'), value: 'FINISHED' },
{ label: getStatusLabel('CANCELLED'), value: 'CANCELLED' },
{ label: getStatusLabel('ERROR'), value: 'ERROR' },
{ label: getStatusLabel('WAITING_FOR_USER_DECISION'), value: 'WAITING_FOR_USER_DECISION' },
{ label: getStatusLabel('READY_TO_START'), value: 'READY_TO_START' },
{ label: getStatusLabel('READY_TO_ENCODE'), value: 'READY_TO_ENCODE' },
{ label: getStatusLabel('MEDIAINFO_CHECK'), value: 'MEDIAINFO_CHECK' },
{ label: getStatusLabel('RIPPING'), value: 'RIPPING' },
{ label: getStatusLabel('ENCODING'), value: 'ENCODING' },
{ label: getStatusLabel('ANALYZING'), value: 'ANALYZING' },
{ label: getStatusLabel('METADATA_SELECTION'), value: 'METADATA_SELECTION' }
];

1
site/404.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

6
site/api/index.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
/*!
* Lunr languages, `Danish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.da=function(){this.pipeline.reset(),this.pipeline.add(e.da.trimmer,e.da.stopWordFilter,e.da.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.da.stemmer))},e.da.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA--",e.da.trimmer=e.trimmerSupport.generateTrimmer(e.da.wordCharacters),e.Pipeline.registerFunction(e.da.trimmer,"trimmer-da"),e.da.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){var e,r=f.cursor+3;if(d=f.limit,0<=r&&r<=f.limit){for(a=r;;){if(e=f.cursor,f.in_grouping(w,97,248)){f.cursor=e;break}if(f.cursor=e,e>=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d<a&&(d=a)}}function n(){var e,r;if(f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hi=function(){this.pipeline.reset(),this.pipeline.add(e.hi.trimmer,e.hi.stopWordFilter,e.hi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hi.stemmer))},e.hi.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿa-zA-Z--0-9-",e.hi.trimmer=e.trimmerSupport.generateTrimmer(e.hi.wordCharacters),e.Pipeline.registerFunction(e.hi.trimmer,"trimmer-hi"),e.hi.stopWordFilter=e.generateStopWordFilter("अत अपना अपनी अपने अभी अंदर आदि आप इत्यादि इन इनका इन्हीं इन्हें इन्हों इस इसका इसकी इसके इसमें इसी इसे उन उनका उनकी उनके उनको उन्हीं उन्हें उन्हों उस उसके उसी उसे एक एवं एस ऐसे और कई कर करता करते करना करने करें कहते कहा का काफ़ी कि कितना किन्हें किन्हों किया किर किस किसी किसे की कुछ कुल के को कोई कौन कौनसा गया घर जब जहाँ जा जितना जिन जिन्हें जिन्हों जिस जिसे जीधर जैसा जैसे जो तक तब तरह तिन तिन्हें तिन्हों तिस तिसे तो था थी थे दबारा दिया दुसरा दूसरे दो द्वारा न नके नहीं ना निहायत नीचे ने पर पहले पूरा पे फिर बनी बही बहुत बाद बाला बिलकुल भी भीतर मगर मानो मे में यदि यह यहाँ यही या यिह ये रखें रहा रहे ऱ्वासा लिए लिये लेकिन व वग़ैरह वर्ग वह वहाँ वहीं वाले वुह वे वो सकता सकते सबसे सभी साथ साबुत साभ सारा से सो संग ही हुआ हुई हुए है हैं हो होता होती होते होना होने".split(" ")),e.hi.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.hi.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var t=i.toString().toLowerCase().replace(/^\s+/,"");return r.cut(t).split("|")},e.Pipeline.registerFunction(e.hi.stemmer,"stemmer-hi"),e.Pipeline.registerFunction(e.hi.stopWordFilter,"stopWordFilter-hi")}});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hy=function(){this.pipeline.reset(),this.pipeline.add(e.hy.trimmer,e.hy.stopWordFilter)},e.hy.wordCharacters="[A-Za-z԰-֏ff-ﭏ]",e.hy.trimmer=e.trimmerSupport.generateTrimmer(e.hy.wordCharacters),e.Pipeline.registerFunction(e.hy.trimmer,"trimmer-hy"),e.hy.stopWordFilter=e.generateStopWordFilter("դու և եք էիր էիք հետո նաև նրանք որը վրա է որ պիտի են այս մեջ ն իր ու ի այդ որոնք այն կամ էր մի ես համար այլ իսկ էին ենք հետ ին թ էինք մենք նրա նա դուք եմ էի ըստ որպես ում".split(" ")),e.Pipeline.registerFunction(e.hy.stopWordFilter,"stopWordFilter-hy"),e.hy.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}(),e.Pipeline.registerFunction(e.hy.stemmer,"stemmer-hy")}});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n<p.length;n++)r?a.push(new e.Token(p[n],{position:[f,p[n].length],index:a.length})):a.push(p[n]),f+=p[n].length;l=c+1}return a},e.ja.stemmer=function(){return function(e){return e}}(),e.Pipeline.registerFunction(e.ja.stemmer,"stemmer-ja"),e.ja.wordCharacters="一二三四五六七八九十百千万億兆一-龠々〆ヵヶぁ-んァ-ヴーア-ン゙a-zA-Z--0-9-",e.ja.trimmer=e.trimmerSupport.generateTrimmer(e.ja.wordCharacters),e.Pipeline.registerFunction(e.ja.trimmer,"trimmer-ja"),e.ja.stopWordFilter=e.generateStopWordFilter("これ それ あれ この その あの ここ そこ あそこ こちら どこ だれ なに なん 何 私 貴方 貴方方 我々 私達 あの人 あのかた 彼女 彼 です あります おります います は が の に を で え から まで より も どの と し それで しかし".split(" ")),e.Pipeline.registerFunction(e.ja.stopWordFilter,"stopWordFilter-ja"),e.jp=e.ja,e.Pipeline.registerFunction(e.jp.stemmer,"stemmer-jp"),e.Pipeline.registerFunction(e.jp.trimmer,"trimmer-jp"),e.Pipeline.registerFunction(e.jp.stopWordFilter,"stopWordFilter-jp")}});

View File

@@ -0,0 +1 @@
module.exports=require("./lunr.ja");

View File

@@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.kn=function(){this.pipeline.reset(),this.pipeline.add(e.kn.trimmer,e.kn.stopWordFilter,e.kn.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.kn.stemmer))},e.kn.wordCharacters="ಀ-಄ಅ-ಔಕ-ಹಾ-ೌ಼-ಽೕ-ೖೝ-ೞೠ-ೡೢ-ೣ೤೥೦-೯ೱ-ೳ",e.kn.trimmer=e.trimmerSupport.generateTrimmer(e.kn.wordCharacters),e.Pipeline.registerFunction(e.kn.trimmer,"trimmer-kn"),e.kn.stopWordFilter=e.generateStopWordFilter("ಮತ್ತು ಈ ಒಂದು ರಲ್ಲಿ ಹಾಗೂ ಎಂದು ಅಥವಾ ಇದು ರ ಅವರು ಎಂಬ ಮೇಲೆ ಅವರ ತನ್ನ ಆದರೆ ತಮ್ಮ ನಂತರ ಮೂಲಕ ಹೆಚ್ಚು ನ ಆ ಕೆಲವು ಅನೇಕ ಎರಡು ಹಾಗು ಪ್ರಮುಖ ಇದನ್ನು ಇದರ ಸುಮಾರು ಅದರ ಅದು ಮೊದಲ ಬಗ್ಗೆ ನಲ್ಲಿ ರಂದು ಇತರ ಅತ್ಯಂತ ಹೆಚ್ಚಿನ ಸಹ ಸಾಮಾನ್ಯವಾಗಿ ನೇ ಹಲವಾರು ಹೊಸ ದಿ ಕಡಿಮೆ ಯಾವುದೇ ಹೊಂದಿದೆ ದೊಡ್ಡ ಅನ್ನು ಇವರು ಪ್ರಕಾರ ಇದೆ ಮಾತ್ರ ಕೂಡ ಇಲ್ಲಿ ಎಲ್ಲಾ ವಿವಿಧ ಅದನ್ನು ಹಲವು ರಿಂದ ಕೇವಲ ದ ದಕ್ಷಿಣ ಗೆ ಅವನ ಅತಿ ನೆಯ ಬಹಳ ಕೆಲಸ ಎಲ್ಲ ಪ್ರತಿ ಇತ್ಯಾದಿ ಇವು ಬೇರೆ ಹೀಗೆ ನಡುವೆ ಇದಕ್ಕೆ ಎಸ್ ಇವರ ಮೊದಲು ಶ್ರೀ ಮಾಡುವ ಇದರಲ್ಲಿ ರೀತಿಯ ಮಾಡಿದ ಕಾಲ ಅಲ್ಲಿ ಮಾಡಲು ಅದೇ ಈಗ ಅವು ಗಳು ಎ ಎಂಬುದು ಅವನು ಅಂದರೆ ಅವರಿಗೆ ಇರುವ ವಿಶೇಷ ಮುಂದೆ ಅವುಗಳ ಮುಂತಾದ ಮೂಲ ಬಿ ಮೀ ಒಂದೇ ಇನ್ನೂ ಹೆಚ್ಚಾಗಿ ಮಾಡಿ ಅವರನ್ನು ಇದೇ ಯ ರೀತಿಯಲ್ಲಿ ಜೊತೆ ಅದರಲ್ಲಿ ಮಾಡಿದರು ನಡೆದ ಆಗ ಮತ್ತೆ ಪೂರ್ವ ಆತ ಬಂದ ಯಾವ ಒಟ್ಟು ಇತರೆ ಹಿಂದೆ ಪ್ರಮಾಣದ ಗಳನ್ನು ಕುರಿತು ಯು ಆದ್ದರಿಂದ ಅಲ್ಲದೆ ನಗರದ ಮೇಲಿನ ಏಕೆಂದರೆ ರಷ್ಟು ಎಂಬುದನ್ನು ಬಾರಿ ಎಂದರೆ ಹಿಂದಿನ ಆದರೂ ಆದ ಸಂಬಂಧಿಸಿದ ಮತ್ತೊಂದು ಸಿ ಆತನ ".split(" ")),e.kn.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.kn.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var n=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(n).split("|")},e.Pipeline.registerFunction(e.kn.stemmer,"stemmer-kn"),e.Pipeline.registerFunction(e.kn.stopWordFilter,"stopWordFilter-kn")}});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){e.multiLanguage=function(){for(var t=Array.prototype.slice.call(arguments),i=t.join("-"),r="",n=[],s=[],p=0;p<t.length;++p)"en"==t[p]?(r+="\\w",n.unshift(e.stopWordFilter),n.push(e.stemmer),s.push(e.stemmer)):(r+=e[t[p]].wordCharacters,e[t[p]].stopWordFilter&&n.unshift(e[t[p]].stopWordFilter),e[t[p]].stemmer&&(n.push(e[t[p]].stemmer),s.push(e[t[p]].stemmer)));var o=e.trimmerSupport.generateTrimmer(r);return e.Pipeline.registerFunction(o,"lunr-multi-trimmer-"+i),n.unshift(o),function(){this.pipeline.reset(),this.pipeline.add.apply(this.pipeline,n),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add.apply(this.searchPipeline,s))}}}});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
/*!
* Lunr languages, `Norwegian` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA--",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a<s&&(a=s)}}function i(){var e,r,n;if(w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sa=function(){this.pipeline.reset(),this.pipeline.add(e.sa.trimmer,e.sa.stopWordFilter,e.sa.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sa.stemmer))},e.sa.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿ꣠-꣱ꣲ-ꣷ꣸-ꣻ꣼-ꣽꣾ-ꣿᆰ0-ᆰ9",e.sa.trimmer=e.trimmerSupport.generateTrimmer(e.sa.wordCharacters),e.Pipeline.registerFunction(e.sa.trimmer,"trimmer-sa"),e.sa.stopWordFilter=e.generateStopWordFilter('तथा अयम्‌ एकम्‌ इत्यस्मिन्‌ तथा तत्‌ वा अयम्‌ इत्यस्य ते आहूत उपरि तेषाम्‌ किन्तु तेषाम्‌ तदा इत्यनेन अधिकः इत्यस्य तत्‌ केचन बहवः द्वि तथा महत्वपूर्णः अयम्‌ अस्य विषये अयं अस्ति तत्‌ प्रथमः विषये इत्युपरि इत्युपरि इतर अधिकतमः अधिकः अपि सामान्यतया ठ इतरेतर नूतनम्‌ द न्यूनम्‌ कश्चित्‌ वा विशालः द सः अस्ति तदनुसारम् तत्र अस्ति केवलम्‌ अपि अत्र सर्वे विविधाः तत्‌ बहवः यतः इदानीम्‌ द दक्षिण इत्यस्मै तस्य उपरि नथ अतीव कार्यम्‌ सर्वे एकैकम्‌ इत्यादि। एते सन्ति उत इत्थम्‌ मध्ये एतदर्थं . स कस्य प्रथमः श्री. करोति अस्मिन् प्रकारः निर्मिता कालः तत्र कर्तुं समान अधुना ते सन्ति स एकः अस्ति सः अर्थात् तेषां कृते . स्थितम् विशेषः अग्रिम तेषाम्‌ समान स्रोतः ख म समान इदानीमपि अधिकतया करोतु ते समान इत्यस्य वीथी सह यस्मिन् कृतवान्‌ धृतः तदा पुनः पूर्वं सः आगतः किम्‌ कुल इतर पुरा मात्रा स विषये उ अतएव अपि नगरस्य उपरि यतः प्रतिशतं कतरः कालः साधनानि भूत तथापि जात सम्बन्धि अन्यत्‌ ग अतः अस्माकं स्वकीयाः अस्माकं इदानीं अन्तः इत्यादयः भवन्तः इत्यादयः एते एताः तस्य अस्य इदम् एते तेषां तेषां तेषां तान् तेषां तेषां तेषां समानः सः एकः च तादृशाः बहवः अन्ये च वदन्ति यत् कियत् कस्मै कस्मै यस्मै यस्मै यस्मै यस्मै न अतिनीचः किन्तु प्रथमं सम्पूर्णतया ततः चिरकालानन्तरं पुस्तकं सम्पूर्णतया अन्तः किन्तु अत्र वा इह इव श्रद्धाय अवशिष्यते परन्तु अन्ये वर्गाः सन्ति ते सन्ति शक्नुवन्ति सर्वे मिलित्वा सर्वे एकत्र"'.split(" ")),e.sa.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.sa.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var i=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(i).split("|")},e.Pipeline.registerFunction(e.sa.stemmer,"stemmer-sa"),e.Pipeline.registerFunction(e.sa.stopWordFilter,"stopWordFilter-sa")}});

View File

@@ -0,0 +1 @@
!function(r,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(r.lunr)}(this,function(){return function(r){r.stemmerSupport={Among:function(r,t,i,s){if(this.toCharArray=function(r){for(var t=r.length,i=new Array(t),s=0;s<t;s++)i[s]=r.charCodeAt(s);return i},!r&&""!=r||!t&&0!=t||!i)throw"Bad Among initialisation: s:"+r+", substring_i: "+t+", result: "+i;this.s_size=r.length,this.s=this.toCharArray(r),this.substring_i=t,this.result=i,this.method=s},SnowballProgram:function(){var r;return{bra:0,ket:0,limit:0,cursor:0,limit_backward:0,setCurrent:function(t){r=t,this.cursor=0,this.limit=t.length,this.limit_backward=0,this.bra=this.cursor,this.ket=this.limit},getCurrent:function(){var t=r;return r=null,t},in_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e>s||e<i)return this.cursor++,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e<i)return this.cursor--,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor+s)!=i.charCodeAt(s))return!1;return this.cursor+=t,!0},eq_s_b:function(t,i){if(this.cursor-this.limit_backward<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor-t+s)!=i.charCodeAt(s))return!1;return this.cursor-=t,!0},find_among:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=l;m<_.s_size;m++){if(n+l==u){f=-1;break}if(f=r.charCodeAt(n+l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=_.s_size-1-l;m>=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}});

View File

@@ -0,0 +1,18 @@
/*!
* Lunr languages, `Swedish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA--",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o<a&&(o=a)}}function t(){var e,r=w.limit_backward;if(w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}});

View File

@@ -0,0 +1 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ta=function(){this.pipeline.reset(),this.pipeline.add(e.ta.trimmer,e.ta.stopWordFilter,e.ta.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ta.stemmer))},e.ta.wordCharacters="஀-உஊ-ஏஐ-ஙச-ட஠-னப-யர-ஹ஺-ிீ-௉ொ-௏ௐ-௙௚-௟௠-௩௪-௯௰-௹௺-௿a-zA-Z--0-9-",e.ta.trimmer=e.trimmerSupport.generateTrimmer(e.ta.wordCharacters),e.Pipeline.registerFunction(e.ta.trimmer,"trimmer-ta"),e.ta.stopWordFilter=e.generateStopWordFilter("அங்கு அங்கே அது அதை அந்த அவர் அவர்கள் அவள் அவன் அவை ஆக ஆகவே ஆகையால் ஆதலால் ஆதலினால் ஆனாலும் ஆனால் இங்கு இங்கே இது இதை இந்த இப்படி இவர் இவர்கள் இவள் இவன் இவை இவ்வளவு உனக்கு உனது உன் உன்னால் எங்கு எங்கே எது எதை எந்த எப்படி எவர் எவர்கள் எவள் எவன் எவை எவ்வளவு எனக்கு எனது எனவே என் என்ன என்னால் ஏது ஏன் தனது தன்னால் தானே தான் நாங்கள் நாம் நான் நீ நீங்கள்".split(" ")),e.ta.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.ta.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.ta.stemmer,"stemmer-ta"),e.Pipeline.registerFunction(e.ta.stopWordFilter,"stopWordFilter-ta")}});

View File

@@ -0,0 +1 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.te=function(){this.pipeline.reset(),this.pipeline.add(e.te.trimmer,e.te.stopWordFilter,e.te.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.te.stemmer))},e.te.wordCharacters="ఀ-ఄఅ-ఔక-హా-ౌౕ-ౖౘ-ౚౠ-ౡౢ-ౣ౦-౯౸-౿఼ఽ్ౝ౷౤౥",e.te.trimmer=e.trimmerSupport.generateTrimmer(e.te.wordCharacters),e.Pipeline.registerFunction(e.te.trimmer,"trimmer-te"),e.te.stopWordFilter=e.generateStopWordFilter("అందరూ అందుబాటులో అడగండి అడగడం అడ్డంగా అనుగుణంగా అనుమతించు అనుమతిస్తుంది అయితే ఇప్పటికే ఉన్నారు ఎక్కడైనా ఎప్పుడు ఎవరైనా ఎవరో ఏ ఏదైనా ఏమైనప్పటికి ఒక ఒకరు కనిపిస్తాయి కాదు కూడా గా గురించి చుట్టూ చేయగలిగింది తగిన తర్వాత దాదాపు దూరంగా నిజంగా పై ప్రకారం ప్రక్కన మధ్య మరియు మరొక మళ్ళీ మాత్రమే మెచ్చుకో వద్ద వెంట వేరుగా వ్యతిరేకంగా సంబంధం".split(" ")),e.te.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.te.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.te.stemmer,"stemmer-te"),e.Pipeline.registerFunction(e.te.stopWordFilter,"stopWordFilter-te")}});

View File

@@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}});

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}});

View File

@@ -0,0 +1 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("@node-rs/jieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 為 以 于 於 上 他 而 后 後 之 来 來 及 了 因 下 可 到 由 这 這 与 與 也 此 但 并 並 个 個 其 已 无 無 小 我 们 們 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 當 从 從 得 打 凡 儿 兒 尔 爾 该 該 各 给 給 跟 和 何 还 還 即 几 幾 既 看 据 據 距 靠 啦 另 么 麽 每 嘛 拿 哪 您 凭 憑 且 却 卻 让 讓 仍 啥 如 若 使 谁 誰 虽 雖 随 隨 同 所 她 哇 嗡 往 些 向 沿 哟 喲 用 咱 则 則 怎 曾 至 致 着 著 诸 諸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}});

View File

@@ -0,0 +1,206 @@
/**
* export the module via AMD, CommonJS or as a browser global
* Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
*/
;(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory)
} else if (typeof exports === 'object') {
/**
* Node. Does not work with strict CommonJS, but
* only CommonJS-like environments that support module.exports,
* like Node.
*/
module.exports = factory()
} else {
// Browser globals (root is window)
factory()(root.lunr);
}
}(this, function () {
/**
* Just return a value to define the module export.
* This example returns an object, but the module
* can return a function as the exported value.
*/
return function(lunr) {
// TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript
// (c) 2008 Taku Kudo <taku@chasen.org>
// TinySegmenter is freely distributable under the terms of a new BSD licence.
// For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt
function TinySegmenter() {
var patterns = {
"[一二三四五六七八九十百千万億兆]":"M",
"[一-龠々〆ヵヶ]":"H",
"[ぁ-ん]":"I",
"[ァ-ヴーア-ン゙ー]":"K",
"[a-zA-Z--]":"A",
"[0-9-]":"N"
}
this.chartype_ = [];
for (var i in patterns) {
var regexp = new RegExp(i);
this.chartype_.push([regexp, patterns[i]]);
}
this.BIAS__ = -332
this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378};
this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920};
this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266};
this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352};
this.BP2__ = {"BO":60,"OO":-1762};
this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965};
this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146};
this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699};
this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973};
this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682};
this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"":-669};
this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990};
this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832};
this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649};
this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393};
this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841};
this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68};
this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591};
this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685};
this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156};
this.TW1__ = {"につい":-4681,"東京都":2026};
this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216};
this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287};
this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865};
this.UC1__ = {"A":484,"K":93,"M":645,"O":-505};
this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646};
this.UC3__ = {"A":-1370,"I":2311};
this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646};
this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831};
this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387};
this.UP1__ = {"O":-214};
this.UP2__ = {"B":69,"O":935};
this.UP3__ = {"B":189};
this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422};
this.UQ2__ = {"BH":216,"BI":113,"OK":1759};
this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212};
this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135};
this.UW2__ = {",":-829,"、":-829,"":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568};
this.UW3__ = {",":4889,"1":-800,"":-1723,"、":4889,"々":-2311,"":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278};
this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637};
this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"":-514,"":-32768,"「":363,"イ":241,"ル":451,"ン":-343};
this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"":-270,"":306,"ル":-673,"ン":-496};
return this;
}
TinySegmenter.prototype.ctype_ = function(str) {
for (var i in this.chartype_) {
if (str.match(this.chartype_[i][0])) {
return this.chartype_[i][1];
}
}
return "O";
}
TinySegmenter.prototype.ts_ = function(v) {
if (v) { return v; }
return 0;
}
TinySegmenter.prototype.segment = function(input) {
if (input == null || input == undefined || input == "") {
return [];
}
var result = [];
var seg = ["B3","B2","B1"];
var ctype = ["O","O","O"];
var o = input.split("");
for (i = 0; i < o.length; ++i) {
seg.push(o[i]);
ctype.push(this.ctype_(o[i]))
}
seg.push("E1");
seg.push("E2");
seg.push("E3");
ctype.push("O");
ctype.push("O");
ctype.push("O");
var word = seg[3];
var p1 = "U";
var p2 = "U";
var p3 = "U";
for (var i = 4; i < seg.length - 3; ++i) {
var score = this.BIAS__;
var w1 = seg[i-3];
var w2 = seg[i-2];
var w3 = seg[i-1];
var w4 = seg[i];
var w5 = seg[i+1];
var w6 = seg[i+2];
var c1 = ctype[i-3];
var c2 = ctype[i-2];
var c3 = ctype[i-1];
var c4 = ctype[i];
var c5 = ctype[i+1];
var c6 = ctype[i+2];
score += this.ts_(this.UP1__[p1]);
score += this.ts_(this.UP2__[p2]);
score += this.ts_(this.UP3__[p3]);
score += this.ts_(this.BP1__[p1 + p2]);
score += this.ts_(this.BP2__[p2 + p3]);
score += this.ts_(this.UW1__[w1]);
score += this.ts_(this.UW2__[w2]);
score += this.ts_(this.UW3__[w3]);
score += this.ts_(this.UW4__[w4]);
score += this.ts_(this.UW5__[w5]);
score += this.ts_(this.UW6__[w6]);
score += this.ts_(this.BW1__[w2 + w3]);
score += this.ts_(this.BW2__[w3 + w4]);
score += this.ts_(this.BW3__[w4 + w5]);
score += this.ts_(this.TW1__[w1 + w2 + w3]);
score += this.ts_(this.TW2__[w2 + w3 + w4]);
score += this.ts_(this.TW3__[w3 + w4 + w5]);
score += this.ts_(this.TW4__[w4 + w5 + w6]);
score += this.ts_(this.UC1__[c1]);
score += this.ts_(this.UC2__[c2]);
score += this.ts_(this.UC3__[c3]);
score += this.ts_(this.UC4__[c4]);
score += this.ts_(this.UC5__[c5]);
score += this.ts_(this.UC6__[c6]);
score += this.ts_(this.BC1__[c2 + c3]);
score += this.ts_(this.BC2__[c3 + c4]);
score += this.ts_(this.BC3__[c4 + c5]);
score += this.ts_(this.TC1__[c1 + c2 + c3]);
score += this.ts_(this.TC2__[c2 + c3 + c4]);
score += this.ts_(this.TC3__[c3 + c4 + c5]);
score += this.ts_(this.TC4__[c4 + c5 + c6]);
// score += this.ts_(this.TC5__[c4 + c5 + c6]);
score += this.ts_(this.UQ1__[p1 + c1]);
score += this.ts_(this.UQ2__[p2 + c2]);
score += this.ts_(this.UQ3__[p3 + c3]);
score += this.ts_(this.BQ1__[p2 + c2 + c3]);
score += this.ts_(this.BQ2__[p2 + c3 + c4]);
score += this.ts_(this.BQ3__[p3 + c2 + c3]);
score += this.ts_(this.BQ4__[p3 + c3 + c4]);
score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]);
score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]);
score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]);
score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]);
var p = "O";
if (score > 0) {
result.push(word);
word = "";
p = "B";
}
p1 = p2;
p2 = p3;
p3 = p;
word += seg[i];
}
result.push(word);
return result;
}
lunr.TinySegmenter = TinySegmenter;
};
}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"version":3,"sources":["src/templates/assets/stylesheets/palette/_scheme.scss","../../../../src/templates/assets/stylesheets/palette.scss","src/templates/assets/stylesheets/palette/_accent.scss","src/templates/assets/stylesheets/palette/_primary.scss","src/templates/assets/stylesheets/utilities/_break.scss"],"names":[],"mappings":"AA2BA,cAGE,6BAME,sDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CACA,mDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CAGA,mDAAA,CACA,gDAAA,CACA,yDAAA,CACA,4DAAA,CAGA,0BAAA,CACA,mCAAA,CAGA,iCAAA,CACA,kCAAA,CACA,mCAAA,CACA,mCAAA,CACA,kCAAA,CACA,iCAAA,CACA,+CAAA,CACA,6DAAA,CACA,gEAAA,CACA,4DAAA,CACA,4DAAA,CACA,6DAAA,CAGA,6CAAA,CAGA,+CAAA,CAGA,uDAAA,CACA,6DAAA,CACA,2DAAA,CAGA,iCAAA,CAGA,yDAAA,CACA,iEAAA,CAGA,mDAAA,CACA,mDAAA,CAGA,qDAAA,CACA,uDAAA,CAGA,8DAAA,CAKA,8DAAA,CAKA,0DAAA,CAzEA,iBCiBF,CD6DE,kHAEE,YC3DJ,CDkFE,yDACE,4BChFJ,CD+EE,2DACE,4BC7EJ,CD4EE,gEACE,4BC1EJ,CDyEE,2DACE,4BCvEJ,CDsEE,yDACE,4BCpEJ,CDmEE,0DACE,4BCjEJ,CDgEE,gEACE,4BC9DJ,CD6DE,0DACE,4BC3DJ,CD0DE,2OACE,4BC/CJ,CDsDA,+FAGE,iCCpDF,CACF,CCjDE,2BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD6CN,CCvDE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDoDN,CC9DE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD2DN,CCrEE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDkEN,CC5EE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDyEN,CCnFE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDgFN,CC1FE,kCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDuFN,CCjGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD8FN,CCxGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDqGN,CC/GE,6BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD4GN,CCtHE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDmHN,CC7HE,4BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD6HN,CCpIE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDoIN,CC3IE,6BACE,yBAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD2IN,CClJE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDkJN,CCzJE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDsJN,CE3JE,4BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwJN,CEnKE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgKN,CE3KE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwKN,CEnLE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgLN,CE3LE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwLN,CEnME,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgMN,CE3ME,mCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwMN,CEnNE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgNN,CE3NE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwNN,CEnOE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgON,CE3OE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwON,CEnPE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFmPN,CE3PE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCF2PN,CEnQE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFmQN,CE3QE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCF2QN,CEnRE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgRN,CE3RE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwRN,CEnSE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BF4RN,CE5SE,kCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BFqSN,CEtRE,sEACE,4BFyRJ,CE1RE,+DACE,4BF6RJ,CE9RE,iEACE,4BFiSJ,CElSE,gEACE,4BFqSJ,CEtSE,iEACE,4BFySJ,CEhSA,8BACE,mDAAA,CACA,4DAAA,CACA,0DAAA,CACA,oDAAA,CACA,2DAAA,CAGA,4BFiSF,CE9RE,yCACE,+BFgSJ,CE7RI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCFiSN,CG7MI,mCD1EA,+CACE,8CF0RJ,CEvRI,qDACE,8CFyRN,CEpRE,iEACE,mCFsRJ,CACF,CGxNI,sCDvDA,uCACE,oCFkRJ,CACF,CEzQA,8BACE,kDAAA,CACA,4DAAA,CACA,wDAAA,CACA,oDAAA,CACA,6DAAA,CAGA,4BF0QF,CEvQE,yCACE,+BFyQJ,CEtQI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCF0QN,CEnQE,yCACE,6CFqQJ,CG9NI,0CDhCA,8CACE,gDFiQJ,CACF,CGnOI,0CDvBA,iFACE,6CF6PJ,CACF,CG3PI,sCDKA,uCACE,6CFyPJ,CACF","file":"palette.css"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

32
site/index.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
site/pipeline/index.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

127
site/sitemap.xml Normal file
View File

@@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://mboehmlaender.github.io/ripster/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/api/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/api/history/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/api/pipeline/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/api/settings/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/api/websocket/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/architecture/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/architecture/backend/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/architecture/database/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/architecture/frontend/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/architecture/overview/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/configuration/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/configuration/environment/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/configuration/settings-reference/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/deployment/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/deployment/development/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/deployment/production/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/getting-started/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/getting-started/configuration/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/getting-started/installation/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/getting-started/prerequisites/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/getting-started/quickstart/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/pipeline/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/pipeline/encoding/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/pipeline/playlist-analysis/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/pipeline/post-encode-scripts/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/pipeline/workflow/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/tools/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/tools/handbrake/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/tools/makemkv/</loc>
<lastmod>2026-03-05</lastmod>
</url>
<url>
<loc>https://mboehmlaender.github.io/ripster/tools/mediainfo/</loc>
<lastmod>2026-03-05</lastmod>
</url>
</urlset>

BIN
site/sitemap.xml.gz Normal file

Binary file not shown.

150
site/stylesheets/extra.css Normal file
View File

@@ -0,0 +1,150 @@
/* Ripster custom styles */
:root {
--md-primary-fg-color: #6a1b9a;
--md-accent-fg-color: #ab47bc;
}
/* Cards grid layout */
.grid.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
margin: 1.5rem 0;
}
.grid.cards > * {
border-radius: 0.5rem;
border: 1px solid var(--md-default-fg-color--lightest);
padding: 1.25rem;
transition: box-shadow 0.2s ease;
}
.grid.cards > *:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
/* Status badge colors */
.status-idle { color: #78909c; }
.status-analyzing { color: #fb8c00; }
.status-ripping { color: #1976d2; }
.status-encoding { color: #7b1fa2; }
.status-finished { color: #388e3c; }
.status-error { color: #d32f2f; }
/* Code blocks */
.md-typeset pre > code {
font-size: 0.85em;
}
/* Mermaid diagrams standard */
.md-typeset .mermaid {
display: flex;
justify-content: center;
margin: 1.5rem 0;
}
/* Large pipeline flowchart: horizontal scroll + min-height */
.pipeline-diagram {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
margin: 1.5rem 0;
padding-bottom: 0.5rem;
}
.pipeline-diagram .mermaid {
min-width: 900px;
justify-content: flex-start;
}
.pipeline-diagram .mermaid svg {
min-width: 900px;
height: auto;
}
/* Pipeline step track */
.pipeline-steps {
display: flex;
flex-wrap: nowrap;
gap: 0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding: 1rem 0 1.5rem 0;
margin: 1.5rem 0;
scrollbar-width: thin;
}
.pipeline-step {
display: flex;
flex-direction: column;
align-items: center;
min-width: 110px;
flex: 0 0 auto;
position: relative;
}
.pipeline-step:not(:last-child)::after {
content: '→';
position: absolute;
right: -14px;
top: 22px;
font-size: 1.2rem;
color: var(--md-default-fg-color--light);
z-index: 1;
}
.pipeline-step-badge {
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 700;
margin-bottom: 0.5rem;
border: 2px solid transparent;
}
.pipeline-step-label {
font-size: 0.7rem;
text-align: center;
line-height: 1.2;
max-width: 90px;
color: var(--md-default-fg-color);
}
.pipeline-step-sub {
font-size: 0.6rem;
color: var(--md-default-fg-color--light);
text-align: center;
margin-top: 0.2rem;
}
/* Step colors */
.step-idle { background: #eceff1; color: #546e7a; border-color: #90a4ae; }
.step-user { background: #e8eaf6; color: #3949ab; border-color: #7986cb; }
.step-running { background: #e3f2fd; color: #1565c0; border-color: #42a5f5; }
.step-wait { background: #fff8e1; color: #e65100; border-color: #ffa726; }
.step-encode { background: #f3e5f5; color: #6a1b9a; border-color: #ab47bc; }
.step-done { background: #e8f5e9; color: #2e7d32; border-color: #66bb6a; }
.step-error { background: #ffebee; color: #c62828; border-color: #ef5350; }
/* Table improvements */
.md-typeset table:not([class]) {
width: 100%;
}
.md-typeset table:not([class]) th {
background-color: var(--md-primary-fg-color);
color: white;
}
/* Admonition tweaks */
.md-typeset .admonition.tip {
border-color: #00897b;
}
.md-typeset .admonition.tip > .admonition-title {
background-color: rgba(0, 137, 123, 0.1);
}

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More