111 行
2.8 KiB
TypeScript
111 行
2.8 KiB
TypeScript
import type { PostImportSourceIssue } from '@/lib/postImportTypes'
|
|
|
|
const MAX_ROWS = 100
|
|
const MAX_URL_BYTES = 20 * 1024
|
|
|
|
|
|
const truncateUrl = (value: string): string =>
|
|
value.length > 120 ? `${ value.slice (0, 117) }…` : value
|
|
|
|
|
|
const bytesize = (value: string): number =>
|
|
new TextEncoder ().encode (value).length
|
|
|
|
|
|
const normaliseImportUrl = (value: string): string | null => {
|
|
const trimmed = value.trim ()
|
|
if (!(trimmed))
|
|
return null
|
|
|
|
try
|
|
{
|
|
const url = new URL (trimmed)
|
|
if (!(url.protocol === 'http:' || url.protocol === 'https:'))
|
|
return null
|
|
if (!(url.host))
|
|
return null
|
|
|
|
url.hostname = url.hostname.toLowerCase ()
|
|
if (url.pathname.endsWith ('/'))
|
|
url.pathname = url.pathname.replace (/\/+$/, '')
|
|
return url.toString ()
|
|
}
|
|
catch
|
|
{
|
|
return null
|
|
}
|
|
}
|
|
|
|
|
|
export const countImportSourceLines = (source: string): number =>
|
|
source
|
|
.split (/\r\n|\n|\r/)
|
|
.map (_1 => _1.trim ())
|
|
.filter (_1 => _1 !== '')
|
|
.length
|
|
|
|
|
|
export const validateImportSource = (
|
|
source: string,
|
|
): PostImportSourceIssue[] => {
|
|
const lines = source.split (/\r\n|\n|\r/)
|
|
const issues: PostImportSourceIssue[] = []
|
|
const seen = new Map<string, number> ()
|
|
let count = 0
|
|
|
|
lines.forEach ((rawLine, index) => {
|
|
const value = rawLine.trim ()
|
|
if (!(value))
|
|
return
|
|
|
|
++count
|
|
const sourceRow = index + 1
|
|
const displayUrl = truncateUrl (value)
|
|
const normalised = normaliseImportUrl (value)
|
|
if (count > MAX_ROWS)
|
|
{
|
|
issues.push ({
|
|
sourceRow,
|
|
message: `取込件数は ${ MAX_ROWS } 件までです.`,
|
|
url: displayUrl })
|
|
return
|
|
}
|
|
if (!(value.startsWith ('http://') || value.startsWith ('https://')))
|
|
{
|
|
issues.push ({
|
|
sourceRow,
|
|
message: 'HTTP または HTTPS の URL ではありません.',
|
|
url: displayUrl })
|
|
return
|
|
}
|
|
if (bytesize (value) > MAX_URL_BYTES)
|
|
{
|
|
issues.push ({
|
|
sourceRow,
|
|
message: 'URL が長すぎます.',
|
|
url: displayUrl })
|
|
return
|
|
}
|
|
if (normalised == null)
|
|
{
|
|
issues.push ({
|
|
sourceRow,
|
|
message: 'URL の形式が不正です.',
|
|
url: displayUrl })
|
|
return
|
|
}
|
|
const duplicateRow = seen.get (normalised)
|
|
if (duplicateRow != null)
|
|
{
|
|
issues.push ({
|
|
sourceRow,
|
|
message: `${ duplicateRow } 行目と同じ URL です.`,
|
|
url: displayUrl })
|
|
return
|
|
}
|
|
seen.set (normalised, sourceRow)
|
|
})
|
|
|
|
return issues
|
|
}
|