You've already forked AstralRinth
forked from didirus/AstralRinth
Refactor folder structure
This commit is contained in:
43
src/utils/ago.ts
Normal file
43
src/utils/ago.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Human readable elapsed or remaining time (example: 3 minutes)
|
||||
* modified from https://stackoverflow.com/a/67338038/938822
|
||||
*/
|
||||
|
||||
const rft = new Intl.RelativeTimeFormat(
|
||||
typeof navigator !== 'undefined' ? [...navigator.languages] : ['en'],
|
||||
{ numeric: 'auto' }
|
||||
)
|
||||
|
||||
export function ago(
|
||||
/** A Date object, timestamp or string parsable with Date.parse() */
|
||||
date: string | number | Date,
|
||||
/** A Date object, timestamp or string parsable with Date.parse() */
|
||||
nowDate: string | number | Date = Date.now()
|
||||
): string {
|
||||
const SECOND = 1000
|
||||
const MINUTE = 60 * SECOND
|
||||
const HOUR = 60 * MINUTE
|
||||
const DAY = 24 * HOUR
|
||||
const WEEK = 7 * DAY
|
||||
const MONTH = 30 * DAY
|
||||
const YEAR = 365 * DAY
|
||||
const intervals = [
|
||||
{ ge: YEAR, divisor: YEAR, unit: 'year' },
|
||||
{ ge: MONTH, divisor: MONTH, unit: 'month' },
|
||||
{ ge: WEEK, divisor: WEEK, unit: 'week' },
|
||||
{ ge: DAY, divisor: DAY, unit: 'day' },
|
||||
{ ge: HOUR, divisor: HOUR, unit: 'hour' },
|
||||
{ ge: MINUTE, divisor: MINUTE, unit: 'minute' },
|
||||
{ ge: 0, divisor: SECOND, unit: 'seconds' },
|
||||
]
|
||||
const now = typeof nowDate === 'object' ? nowDate.getTime() : new Date(nowDate).getTime()
|
||||
const diff = now - (typeof date === 'object' ? date : new Date(date)).getTime()
|
||||
const diffAbs = Math.abs(diff)
|
||||
for (const interval of intervals) {
|
||||
if (diffAbs >= interval.ge) {
|
||||
const x = Math.round(Math.abs(diff) / interval.divisor)
|
||||
const isFuture = diff < 0
|
||||
return rft.format(isFuture ? x : -x, interval.unit as Intl.RelativeTimeFormatUnit)
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/utils/classCombine.ts
Normal file
3
src/utils/classCombine.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function classCombine(names) {
|
||||
return names.filter((name) => name && !name.includes('undefined')).join(' ')
|
||||
}
|
||||
4
src/utils/index.ts
Normal file
4
src/utils/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { ago } from './ago'
|
||||
export { Permissions } from './permissions'
|
||||
export { formatVersions, getPrimary, downloadUrl } from './versions'
|
||||
export { markdown, markdownInline } from './parse'
|
||||
142
src/utils/parse.ts
Normal file
142
src/utils/parse.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { marked } from 'marked'
|
||||
import hljs from 'highlight.js'
|
||||
import insane from 'insane'
|
||||
|
||||
const renderer = new marked.Renderer()
|
||||
|
||||
renderer.image = (href, text) => {
|
||||
if (/^https?:\/\/(www\.)?youtube\.com\/watch\?v=[a-zA-Z0-9_]{11}$/.test(href)) {
|
||||
const id = href.substring(32, 43)
|
||||
return `<iframe src="https://www.youtube-nocookie.com/embed/${id}?&modestbranding=1&autoplay=0&rel=0" frameborder="0" allowfullscreen></iframe>`
|
||||
} else {
|
||||
return `<img src="${href}" alt="${text}" />`
|
||||
}
|
||||
}
|
||||
|
||||
renderer.link = (href, title, text) => {
|
||||
if (href === null) {
|
||||
return text
|
||||
}
|
||||
let out = '<a href="' + href + '" rel="external nofollow"'
|
||||
if (title) {
|
||||
out += ' title="' + title + '"'
|
||||
}
|
||||
out += '>' + text + '</a>'
|
||||
return out
|
||||
}
|
||||
|
||||
marked.setOptions({
|
||||
renderer,
|
||||
highlight: function (code, lang) {
|
||||
const language = hljs.getLanguage(lang) ? lang : 'plaintext'
|
||||
return hljs.highlight(code, { language }).value
|
||||
},
|
||||
langPrefix: 'hljs language-',
|
||||
headerPrefix: '',
|
||||
gfm: true,
|
||||
smartLists: true,
|
||||
})
|
||||
|
||||
function sanitize(html: string): string {
|
||||
return insane(html, {
|
||||
allowedAttributes: {
|
||||
a: ['href', 'target', 'title', 'rel'],
|
||||
iframe: ['allowfullscreen', 'src', 'width', 'height'],
|
||||
img: ['src', 'width', 'height', 'alt'],
|
||||
h1: ['id'],
|
||||
h2: ['id'],
|
||||
h3: ['id'],
|
||||
h4: ['id'],
|
||||
h5: ['id'],
|
||||
h6: ['id'],
|
||||
code: ['class'],
|
||||
span: ['class'],
|
||||
input: ['type', 'checked', 'disabled'],
|
||||
font: ['color'],
|
||||
},
|
||||
allowedClasses: {},
|
||||
allowedSchemes: ['http', 'https', 'mailto'],
|
||||
allowedTags: [
|
||||
'a',
|
||||
'b',
|
||||
'blockquote',
|
||||
'br',
|
||||
'caption',
|
||||
'center',
|
||||
'code',
|
||||
'del',
|
||||
'details',
|
||||
'div',
|
||||
'em',
|
||||
'font',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'hr',
|
||||
'i',
|
||||
'iframe',
|
||||
'img',
|
||||
'input',
|
||||
'ins',
|
||||
'kbd',
|
||||
'li',
|
||||
'main',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
'span',
|
||||
'strike',
|
||||
'strong',
|
||||
'sub',
|
||||
'summary',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'td',
|
||||
'th',
|
||||
'thead',
|
||||
'tr',
|
||||
'u',
|
||||
'ul',
|
||||
],
|
||||
filter: ({ tag, attrs }): boolean => {
|
||||
if (tag === 'iframe') {
|
||||
return /^https?:\/\/(www\.)?(youtube|youtube-nocookie)\.com\/embed\/[a-zA-Z0-9_]{11}(\?)?(&modestbranding=1)?(&autoplay=0)?(&loop=1)?(&playlist=[a-zA-Z0-9_]{11})?(&rel=0)?$/.test(
|
||||
attrs.src || ''
|
||||
)
|
||||
} else if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)) {
|
||||
return attrs.id !== 'svelte'
|
||||
} else if (tag === 'input') {
|
||||
return attrs.type === 'checkbox' && attrs.disabled === ''
|
||||
} else if (tag === 'code' || tag === 'span') {
|
||||
return !attrs.class || attrs.class.replace(' ', '').startsWith('hljs')
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
transformText: null,
|
||||
})
|
||||
}
|
||||
|
||||
export function markdownInline(markdown: string): string {
|
||||
return insane(
|
||||
marked.parseInline(markdown),
|
||||
{
|
||||
allowedAttributes: {
|
||||
a: ['href', 'target', 'title', 'rel'],
|
||||
},
|
||||
allowedClasses: {},
|
||||
allowedSchemes: ['http', 'https', 'mailto'],
|
||||
allowedTags: ['a', 'b', 'br', 'code', 'em', 'i', 'strike', 'strong', 'sub', 'sup', 'u'],
|
||||
transformText: null,
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
export function markdown(markdown: string): string {
|
||||
return sanitize(marked.parse(markdown))
|
||||
}
|
||||
42
src/utils/permissions.ts
Normal file
42
src/utils/permissions.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export class Permissions {
|
||||
uploadVersions = false
|
||||
deleteVersion = false
|
||||
editDetails = false
|
||||
editBody = false
|
||||
manageInvites = false
|
||||
removeMember = false
|
||||
editMember = false
|
||||
deleteProject = false
|
||||
|
||||
get settingsPage(): boolean {
|
||||
return this.manageInvites || this.removeMember || this.editMember || this.deleteProject
|
||||
}
|
||||
|
||||
toInteger(): number {
|
||||
return (
|
||||
(this.uploadVersions ? 1 : 0) |
|
||||
(this.deleteVersion ? 1 << 1 : 0) |
|
||||
(this.editDetails ? 1 << 2 : 0) |
|
||||
(this.editBody ? 1 << 3 : 0) |
|
||||
(this.manageInvites ? 1 << 4 : 0) |
|
||||
(this.removeMember ? 1 << 5 : 0) |
|
||||
(this.editMember ? 1 << 6 : 0) |
|
||||
(this.deleteProject ? 1 << 7 : 0)
|
||||
)
|
||||
}
|
||||
|
||||
constructor(from: number | 'ALL' | null) {
|
||||
if (from === 'ALL' || from === 0b11111111 || from === null) {
|
||||
Object.keys(this).forEach((v) => (this[v] = true))
|
||||
} else if (typeof from === 'number') {
|
||||
this.uploadVersions = !!(from & (1 << 0))
|
||||
this.deleteVersion = !!(from & (1 << 1))
|
||||
this.editDetails = !!(from & (1 << 2))
|
||||
this.editBody = !!(from & (1 << 3))
|
||||
this.manageInvites = !!(from & (1 << 4))
|
||||
this.removeMember = !!(from & (1 << 5))
|
||||
this.editMember = !!(from & (1 << 6))
|
||||
this.deleteProject = !!(from & (1 << 7))
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/utils/uniqueId.ts
Normal file
6
src/utils/uniqueId.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
let idCounter = 0
|
||||
|
||||
export function uniqueId(prefix = ''): string {
|
||||
const id = ++idCounter
|
||||
return prefix + id
|
||||
}
|
||||
83
src/utils/versions.ts
Normal file
83
src/utils/versions.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import gameVersions from '$generated/gameVersions.json'
|
||||
|
||||
export function formatVersions(versionArray: string[]): string {
|
||||
const allVersions = gameVersions.slice().reverse()
|
||||
const allReleases = allVersions.filter((x) => x.version_type === 'release')
|
||||
|
||||
const intervals = []
|
||||
let currentInterval = 0
|
||||
|
||||
for (let i = 0; i < versionArray.length; i++) {
|
||||
const index = allVersions.findIndex((x) => x.version === versionArray[i])
|
||||
const releaseIndex = allReleases.findIndex((x) => x.version === versionArray[i])
|
||||
|
||||
if (i === 0) {
|
||||
intervals.push([[versionArray[i], index, releaseIndex]])
|
||||
} else {
|
||||
const intervalBase = intervals[currentInterval]
|
||||
|
||||
if (
|
||||
(index - intervalBase[intervalBase.length - 1][1] === 1 ||
|
||||
releaseIndex - intervalBase[intervalBase.length - 1][2] === 1) &&
|
||||
(allVersions[intervalBase[0][1]].version_type === 'release' ||
|
||||
allVersions[index].version_type !== 'release')
|
||||
) {
|
||||
intervalBase[1] = [versionArray[i], index, releaseIndex]
|
||||
} else {
|
||||
currentInterval += 1
|
||||
intervals[currentInterval] = [[versionArray[i], index, releaseIndex]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newIntervals = []
|
||||
for (let i = 0; i < intervals.length; i++) {
|
||||
const interval = intervals[i]
|
||||
|
||||
if (interval.length === 2 && interval[0][2] !== -1 && interval[1][2] === -1) {
|
||||
let lastSnapshot = null
|
||||
for (let j = interval[1][1]; j > interval[0][1]; j--) {
|
||||
if (allVersions[j].version_type === 'release') {
|
||||
newIntervals.push([
|
||||
interval[0],
|
||||
[
|
||||
allVersions[j].version,
|
||||
j,
|
||||
allReleases.findIndex((x) => x.version === allVersions[j].version),
|
||||
],
|
||||
])
|
||||
|
||||
if (lastSnapshot !== null && lastSnapshot !== j + 1) {
|
||||
newIntervals.push([[allVersions[lastSnapshot].version, lastSnapshot, -1], interval[1]])
|
||||
} else {
|
||||
newIntervals.push([interval[1]])
|
||||
}
|
||||
|
||||
break
|
||||
} else {
|
||||
lastSnapshot = j
|
||||
}
|
||||
}
|
||||
} else {
|
||||
newIntervals.push(interval)
|
||||
}
|
||||
}
|
||||
|
||||
const output = []
|
||||
|
||||
for (const interval of newIntervals) {
|
||||
if (interval.length === 2) {
|
||||
output.push(`${interval[0][0]}—${interval[1][0]}`)
|
||||
} else {
|
||||
output.push(interval[0][0])
|
||||
}
|
||||
}
|
||||
|
||||
return output.join(', ')
|
||||
}
|
||||
|
||||
export const getPrimary = (files) => files.find((file) => file.primary) || files[0]
|
||||
|
||||
export function downloadUrl(file): string {
|
||||
return import.meta.env.VITE_API_URL + `version_file/${file?.hashes.sha1}/download`
|
||||
}
|
||||
Reference in New Issue
Block a user