Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ Requirements:
- Node v22+
- Tilt v0.33+

Run `tilt up`, and see `http://localhost:10350/` for the Tilt dashboard.
Run `tilt up`, and see http://localhost:10350/ for the Tilt dashboard.

Front-end application is available at `http://localhost:15641/`.
Front-end application is available at http://localhost:15641/.

## Localize to your own language

Expand Down
6 changes: 4 additions & 2 deletions pkg/customization/customize.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ type (
DisableQRSupport bool `json:"disableQRSupport,omitempty" yaml:"disableQRSupport"`
DisableThemeSwitcher bool `json:"disableThemeSwitcher,omitempty" yaml:"disableThemeSwitcher"`

DisableExpiryOverride bool `json:"disableExpiryOverride,omitempty" yaml:"disableExpiryOverride"`
ExpiryChoices []int64 `json:"expiryChoices,omitempty" yaml:"expiryChoices"`
DisableExpiryOverride bool `json:"disableExpiryOverride,omitempty" yaml:"disableExpiryOverride"`
ExpiryChoices []int64 `json:"expiryChoices,omitempty" yaml:"expiryChoices"`
ExpiryChoicesHuman []string `json:"expiryChoicesHuman,omitempty" yaml:"expiryChoicesHuman"`
DefaultExpiryHuman string `json:"defaultExpiryHuman,omitempty" yaml:"defaultExpiryHuman"`

AcceptedFileTypes string `json:"acceptedFileTypes" yaml:"acceptedFileTypes"`
DisableFileAttachment bool `json:"disableFileAttachment" yaml:"disableFileAttachment"`
Expand Down
102 changes: 100 additions & 2 deletions src/components/create.vue
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,11 @@
</template>

<script lang="ts">
import {
bytesToHuman,
durationToSeconds,
} from '../helpers'
import appCrypto from '../crypto.ts'
import { bytesToHuman } from '../helpers'
import { defineComponent } from 'vue'
import FilesDisplay from './fileDisplay.vue'
import GrowArea from './growarea.vue'
Expand All @@ -142,6 +145,19 @@ const defaultExpiryChoices = [
5 * 60, // 5 minutes
]

const defaultExpiryChoicesHuman = [
'90d',
'30d',
'7d',
'3d',
'24h', // or 1d, equivalent
'12h',
'4h',
'1h',
'30m',
'5m',
]

/*
* We define an internal max file-size which cannot get exceeded even
* though the server might accept more: at around 70 MiB the base64
Expand Down Expand Up @@ -169,6 +185,10 @@ export default defineComponent({
},

expiryChoices(): Record<string, string | null>[] {
if (this.customize.expiryChoicesHuman) {
return this.expiryChoicesHuman
}

const choices = [{ text: this.$t('expire-default'), value: null as string | null }]

for (const choice of this.customize.expiryChoices || defaultExpiryChoices) {
Expand All @@ -193,6 +213,26 @@ export default defineComponent({
return choices
},

expiryChoicesHuman() {
const choices = []
if (!this.hasValidDefaultExpiryHuman()) {
choices.push({ text: this.$t('expire-default'), value: null })
}

for (const choice of this.customize.expiryChoicesHuman || defaultExpiryChoicesHuman) {
const option = { value: choice }

const unit = choice.slice(-1)
const amount = parseInt(choice.slice(0, -1), 10)

option.text = this._getTextForAmount(unit, amount)

choices.push(option)
}

return choices
},

invalidFilesSelected(): boolean {
if (this.customize.acceptedFileTypes === '') {
// No limitation configured, no need to check
Expand Down Expand Up @@ -236,13 +276,16 @@ export default defineComponent({

created(): void {
this.checkWriteAccess()

this.initExpiry()
},

data() {
return {
attachedFiles: [],
canWrite: null,
createRunning: false,
expiryInitialized: false,
fileSize: 0,
secret: '',
securePassword: null,
Expand All @@ -254,6 +297,21 @@ export default defineComponent({
emits: ['error', 'navigate'],

methods: {
_getTextForAmount(unit, amount) {
switch (unit) {
case 'd':
return this.$t('expire-n-days', amount)
case 'h':
return this.$t('expire-n-hours', amount)
case 'm':
return this.$t('expire-n-minutes', amount)
case 's':
return this.$t('expire-n-seconds', amount)
}

return amount
},

bytesToHuman,

checkWriteAccess(): Promise<void> {
Expand Down Expand Up @@ -300,7 +358,7 @@ export default defineComponent({
.then(secret => {
let reqURL = 'api/create'
if (this.selectedExpiry !== null) {
reqURL = `api/create?expire=${this.selectedExpiry}`
reqURL = `api/create?expire=${durationToSeconds(this.selectedExpiry)}`
}

return fetch(reqURL, {
Expand Down Expand Up @@ -367,6 +425,34 @@ export default defineComponent({
this.$refs.createSecretFiles.value = ''
},

hasValidDefaultExpiryHuman() {
const defaultExpiry = this.customize.defaultExpiryHuman || false
if (defaultExpiry === false) {
return false
}

if (!this.customize.expiryChoicesHuman) {
return false
}

return this.customize.expiryChoicesHuman.includes(defaultExpiry)
},

initExpiry() {
const match = document.cookie.match(/(?:^|;\s*)selectedExpiry=([^;]*)/)
this.selectedExpiry = match
? decodeURIComponent(match[1])
: this.customize?.defaultExpiryHuman || null

if (!this.customize?.expiryChoicesHuman) {
return
}

if (!this.customize?.expiryChoicesHuman.includes(this.selectedExpiry)) {
this.selectedExpiry = null
}
},

isAcceptedBy(fileMeta: any, accept: string): boolean {
if (/^(?:[a-z]+|\*)\/(?:[a-zA-Z0-9.+_-]+|\*)$/.test(accept)) {
// That's likely supposed to be a mime-type
Expand Down Expand Up @@ -395,5 +481,17 @@ export default defineComponent({
},

name: 'AppCreate',

watch: {
selectedExpiry(newVal) {
if (!this.expiryInitialized) {
this.expiryInitialized = true

return
}

document.cookie = `selectedExpiry=${newVal || ''}; path=/; max-age=${60 * 60 * 24 * 365}`
},
},
})
</script>
27 changes: 26 additions & 1 deletion src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,38 @@ function bytesToHuman(bytes: number): string {
{ thresh: 1024, unit: 'KiB' },
]) {
if (bytes > t.thresh) {
return `${(bytes / t.thresh).toFixed(1)} ${t.unit}`
return `${parseFloat((bytes / t.thresh).toFixed(1))} ${t.unit}`
}
}

return `${bytes} B`
}

function durationToSeconds(duration) {
const regex = /^(\d+)([smhd])$/
const match = typeof duration === 'string' && duration.match(regex)
if (!match) {
return duration
}

const value = parseInt(match[1], 10)
const unit = match[2]

switch (unit) {
case 's':
return value
case 'm':
return value * 60
case 'h':
return value * 3600
case 'd':
return value * 86400
}

return duration // Fallback: return as-is
}

export {
bytesToHuman,
durationToSeconds,
}
Loading