Refactor folder structure

This commit is contained in:
venashial
2022-06-17 19:58:01 -07:00
parent 8204139df8
commit 137fbb638b
110 changed files with 45 additions and 52 deletions

43
src/utils/ago.ts Normal file
View 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)
}
}
}

View 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
View 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
View 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
View 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
View 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
View 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`
}