feat: add listing edit mode with owner detection, fix service worker API caching for external domain
This commit is contained in:
@@ -124,9 +124,10 @@ locales/
|
|||||||
|
|
||||||
1. ~~Seiten für Profil-Dropdown~~ ✅ Fertig (`page-my-listings.js`, `page-messages.js`, `page-favorites.js`, `page-settings.js`)
|
1. ~~Seiten für Profil-Dropdown~~ ✅ Fertig (`page-my-listings.js`, `page-messages.js`, `page-favorites.js`, `page-settings.js`)
|
||||||
2. ~~Suchseite mit Filtern~~ ✅ Merged in `page-home.js`
|
2. ~~Suchseite mit Filtern~~ ✅ Merged in `page-home.js`
|
||||||
3. Payment-Integration mit BTCpay Server (https://pay.xmr.rocks/)
|
3. ~~Listings bearbeiten~~ ✅ Edit-Modus via `#/edit/:id`
|
||||||
4. Reputation-System (5/15/50 Deals Stufen)
|
4. Payment-Integration mit BTCpay Server (https://pay.xmr.rocks/)
|
||||||
5. Push-Benachrichtigungen für neue Nachrichten
|
5. Reputation-System (5/15/50 Deals Stufen)
|
||||||
|
6. Push-Benachrichtigungen für neue Nachrichten
|
||||||
|
|
||||||
## Directus Berechtigungen (Public-Rolle)
|
## Directus Berechtigungen (Public-Rolle)
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ dgray/
|
|||||||
- [x] API-Services (`directus.js`, `listings.js`, `categories.js`, `locations.js`)
|
- [x] API-Services (`directus.js`, `listings.js`, `categories.js`, `locations.js`)
|
||||||
- [x] Directus Public-Berechtigungen (siehe `docs/DIRECTUS-SCHEMA.md`)
|
- [x] Directus Public-Berechtigungen (siehe `docs/DIRECTUS-SCHEMA.md`)
|
||||||
- [x] Neue Seiten: Favoriten, Meine Anzeigen, Nachrichten, Einstellungen
|
- [x] Neue Seiten: Favoriten, Meine Anzeigen, Nachrichten, Einstellungen
|
||||||
|
- [x] Listings bearbeiten (Edit-Modus für eigene Anzeigen)
|
||||||
|
|
||||||
### Phase 3: Kommunikation
|
### Phase 3: Kommunikation
|
||||||
- [x] Chat-System (E2E-verschlüsselt mit NaCl)
|
- [x] Chat-System (E2E-verschlüsselt mit NaCl)
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ Meldungen von Anzeigen.
|
|||||||
|
|
||||||
| Collection | Read | Create | Update | Delete | Hinweise |
|
| Collection | Read | Create | Update | Delete | Hinweise |
|
||||||
|------------|:----:|:------:|:------:|:------:|----------|
|
|------------|:----:|:------:|:------:|:------:|----------|
|
||||||
| `listings` | ✓ | ✓ | ✓* | - | Nur `status=published` lesen, *Update nur `views` (via Flow) |
|
| `listings` | ✓ | ✓ | ✓ | - | Siehe Details unten |
|
||||||
| `listings_files` | ✓ | ✓ | - | - | Für Bilder-Upload |
|
| `listings_files` | ✓ | ✓ | - | - | Für Bilder-Upload |
|
||||||
| `directus_files` | ✓ | ✓ | - | - | Asset-Upload |
|
| `directus_files` | ✓ | ✓ | - | - | Asset-Upload |
|
||||||
| `categories` | ✓ | - | - | - | Nur `status=published` |
|
| `categories` | ✓ | - | - | - | Nur `status=published` |
|
||||||
@@ -205,6 +205,27 @@ Meldungen von Anzeigen.
|
|||||||
| `favorites` | ✓ | ✓ | - | ✓ | Nur eigene |
|
| `favorites` | ✓ | ✓ | - | ✓ | Nur eigene |
|
||||||
| `reports` | - | ✓ | - | - | Nur erstellen |
|
| `reports` | - | ✓ | - | - | Nur erstellen |
|
||||||
|
|
||||||
|
### Listings Update-Berechtigungen (Detail)
|
||||||
|
|
||||||
|
**Custom Filter:**
|
||||||
|
```json
|
||||||
|
{ "user_created": { "_eq": "$CURRENT_USER" } }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Field Permissions (Update):**
|
||||||
|
- `title`, `slug`, `description`
|
||||||
|
- `price`, `currency`, `price_mode`, `price_type`
|
||||||
|
- `category`, `condition`, `location`
|
||||||
|
- `shipping`, `shipping_cost`
|
||||||
|
- `monero_address`
|
||||||
|
- `images`
|
||||||
|
- `views` (geschützt durch Flow)
|
||||||
|
|
||||||
|
**Read Filter:**
|
||||||
|
```json
|
||||||
|
{ "status": { "_eq": "published" } }
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Directus Flows
|
## Directus Flows
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class AppShell extends HTMLElement {
|
|||||||
.register('/search', 'page-home') // Redirect search to home
|
.register('/search', 'page-home') // Redirect search to home
|
||||||
.register('/listing/:id', 'page-listing')
|
.register('/listing/:id', 'page-listing')
|
||||||
.register('/create', 'page-create')
|
.register('/create', 'page-create')
|
||||||
|
.register('/edit/:id', 'page-create')
|
||||||
.register('/favorites', 'page-favorites')
|
.register('/favorites', 'page-favorites')
|
||||||
.register('/my-listings', 'page-my-listings')
|
.register('/my-listings', 'page-my-listings')
|
||||||
.register('/messages', 'page-messages')
|
.register('/messages', 'page-messages')
|
||||||
|
|||||||
@@ -11,7 +11,19 @@ const STORAGE_KEY = 'dgray_create_draft'
|
|||||||
class PageCreate extends HTMLElement {
|
class PageCreate extends HTMLElement {
|
||||||
constructor() {
|
constructor() {
|
||||||
super()
|
super()
|
||||||
this.formData = this.loadDraft() || {
|
this.editMode = false
|
||||||
|
this.editId = null
|
||||||
|
this.existingImages = []
|
||||||
|
this.formData = this.loadDraft() || this.getEmptyFormData()
|
||||||
|
this.imageFiles = []
|
||||||
|
this.imagePreviews = []
|
||||||
|
this.categories = []
|
||||||
|
this.submitting = false
|
||||||
|
this.isNewAccount = true
|
||||||
|
}
|
||||||
|
|
||||||
|
getEmptyFormData() {
|
||||||
|
return {
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
price: '',
|
price: '',
|
||||||
@@ -24,11 +36,6 @@ class PageCreate extends HTMLElement {
|
|||||||
shipping: false,
|
shipping: false,
|
||||||
moneroAddress: ''
|
moneroAddress: ''
|
||||||
}
|
}
|
||||||
this.imageFiles = []
|
|
||||||
this.imagePreviews = []
|
|
||||||
this.categories = []
|
|
||||||
this.submitting = false
|
|
||||||
this.isNewAccount = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDraft() {
|
loadDraft() {
|
||||||
@@ -59,13 +66,63 @@ class PageCreate extends HTMLElement {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasDraft = !!localStorage.getItem(STORAGE_KEY)
|
// Check if edit mode
|
||||||
|
if (this.dataset.id) {
|
||||||
|
this.editMode = true
|
||||||
|
this.editId = this.dataset.id
|
||||||
|
await this.loadExistingListing()
|
||||||
|
} else {
|
||||||
|
this.hasDraft = !!localStorage.getItem(STORAGE_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
await this.loadCategories()
|
await this.loadCategories()
|
||||||
await this.checkAccountStatus()
|
await this.checkAccountStatus()
|
||||||
this.render()
|
this.render()
|
||||||
this.unsubscribe = i18n.subscribe(() => this.render())
|
this.unsubscribe = i18n.subscribe(() => this.render())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadExistingListing() {
|
||||||
|
try {
|
||||||
|
const listing = await directus.getListing(this.editId)
|
||||||
|
|
||||||
|
// Verify ownership
|
||||||
|
const user = await auth.getUser()
|
||||||
|
if (listing.user_created !== user?.id) {
|
||||||
|
window.location.hash = '#/'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.formData = {
|
||||||
|
title: listing.title || '',
|
||||||
|
description: listing.description || '',
|
||||||
|
price: listing.price?.toString() || '',
|
||||||
|
currency: listing.currency || 'EUR',
|
||||||
|
price_mode: listing.price_mode || 'fiat',
|
||||||
|
price_type: listing.price_type || 'fixed',
|
||||||
|
category: listing.category?.id || listing.category || '',
|
||||||
|
condition: listing.condition || 'good',
|
||||||
|
location: listing.location?.id || listing.location || '',
|
||||||
|
shipping: listing.shipping || false,
|
||||||
|
moneroAddress: listing.monero_address || '',
|
||||||
|
status: listing.status || 'published'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store existing images
|
||||||
|
this.existingImages = (listing.images || []).map(img => ({
|
||||||
|
id: img.id,
|
||||||
|
fileId: img.directus_files_id?.id || img.directus_files_id
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Create previews for existing images
|
||||||
|
this.imagePreviews = this.existingImages.map(img =>
|
||||||
|
directus.getThumbnailUrl(img.fileId, 200)
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load listing for edit:', e)
|
||||||
|
window.location.hash = '#/'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async checkAccountStatus() {
|
async checkAccountStatus() {
|
||||||
try {
|
try {
|
||||||
// Check if user has any published listings
|
// Check if user has any published listings
|
||||||
@@ -138,11 +195,13 @@ class PageCreate extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const pageTitle = this.editMode ? t('create.editTitle') : t('create.title')
|
||||||
|
|
||||||
this.innerHTML = /* html */`
|
this.innerHTML = /* html */`
|
||||||
<div class="create-page">
|
<div class="create-page">
|
||||||
<h1 data-i18n="create.title">${t('create.title')}</h1>
|
<h1>${pageTitle}</h1>
|
||||||
|
|
||||||
${this.hasDraft ? `
|
${!this.editMode && this.hasDraft ? `
|
||||||
<div class="draft-notice">
|
<div class="draft-notice">
|
||||||
<span>${t('create.draftRestored')}</span>
|
<span>${t('create.draftRestored')}</span>
|
||||||
<button type="button" class="btn-link" id="clear-draft-btn">${t('create.clearDraft')}</button>
|
<button type="button" class="btn-link" id="clear-draft-btn">${t('create.clearDraft')}</button>
|
||||||
@@ -289,7 +348,7 @@ class PageCreate extends HTMLElement {
|
|||||||
${t('create.cancel')}
|
${t('create.cancel')}
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" class="btn btn-primary btn-lg">
|
<button type="submit" class="btn btn-primary btn-lg">
|
||||||
${t('create.publish')}
|
${this.editMode ? t('create.saveChanges') : t('create.publish')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -417,8 +476,23 @@ class PageCreate extends HTMLElement {
|
|||||||
|
|
||||||
if (this.submitting) return
|
if (this.submitting) return
|
||||||
|
|
||||||
// Validate PoW Captcha (only for new accounts)
|
const form = e.target
|
||||||
if (this.isNewAccount) {
|
|
||||||
|
// Read current form values directly (more reliable than event listeners)
|
||||||
|
const formElements = {
|
||||||
|
title: form.querySelector('#title')?.value || '',
|
||||||
|
description: form.querySelector('#description')?.value || '',
|
||||||
|
price: form.querySelector('#price')?.value || '',
|
||||||
|
currency: form.querySelector('#currency')?.value || 'EUR',
|
||||||
|
price_mode: form.querySelector('#price_mode')?.value || 'fiat',
|
||||||
|
category: form.querySelector('#category')?.value || '',
|
||||||
|
condition: form.querySelector('#condition')?.value || 'good',
|
||||||
|
shipping: form.querySelector('#shipping')?.checked || false,
|
||||||
|
moneroAddress: form.querySelector('#moneroAddress')?.value || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate PoW Captcha (only for new accounts and new listings)
|
||||||
|
if (!this.editMode && this.isNewAccount) {
|
||||||
const captcha = this.querySelector('#pow-captcha')
|
const captcha = this.querySelector('#pow-captcha')
|
||||||
if (!captcha?.isSolved()) {
|
if (!captcha?.isSolved()) {
|
||||||
this.showError(t('captcha.error'))
|
this.showError(t('captcha.error'))
|
||||||
@@ -427,7 +501,7 @@ class PageCreate extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate Monero address
|
// Validate Monero address
|
||||||
if (this.formData.moneroAddress && !this.validateMoneroAddress(this.formData.moneroAddress)) {
|
if (formElements.moneroAddress && !this.validateMoneroAddress(formElements.moneroAddress)) {
|
||||||
this.showError(t('create.invalidMoneroAddress'))
|
this.showError(t('create.invalidMoneroAddress'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -435,35 +509,38 @@ class PageCreate extends HTMLElement {
|
|||||||
this.submitting = true
|
this.submitting = true
|
||||||
this.clearError()
|
this.clearError()
|
||||||
|
|
||||||
const form = e.target
|
|
||||||
const submitBtn = form.querySelector('[type="submit"]')
|
const submitBtn = form.querySelector('[type="submit"]')
|
||||||
submitBtn.disabled = true
|
submitBtn.disabled = true
|
||||||
submitBtn.textContent = t('create.publishing')
|
submitBtn.textContent = this.editMode ? t('create.saving') : t('create.publishing')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Upload images first
|
// Upload new images first
|
||||||
let imageIds = []
|
let newImageIds = []
|
||||||
if (this.imageFiles.length > 0) {
|
if (this.imageFiles.length > 0) {
|
||||||
const uploadedFiles = await directus.uploadMultipleFiles(this.imageFiles)
|
const uploadedFiles = await directus.uploadMultipleFiles(this.imageFiles)
|
||||||
imageIds = uploadedFiles.map(f => f.id)
|
newImageIds = uploadedFiles.map(f => f.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create listing
|
// Build listing data from form values
|
||||||
const listingData = {
|
const listingData = {
|
||||||
title: this.formData.title,
|
title: formElements.title,
|
||||||
slug: this.generateSlug(this.formData.title),
|
slug: this.generateSlug(formElements.title),
|
||||||
description: this.formData.description,
|
description: formElements.description,
|
||||||
price: String(parseFloat(this.formData.price) || 0),
|
price: String(parseFloat(formElements.price) || 0),
|
||||||
currency: this.formData.currency,
|
currency: formElements.currency
|
||||||
status: 'published'
|
}
|
||||||
|
|
||||||
|
// Only set status on create, not on edit
|
||||||
|
if (!this.editMode) {
|
||||||
|
listingData.status = 'published'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add optional fields only if set
|
// Add optional fields only if set
|
||||||
if (this.formData.price_mode) listingData.price_mode = this.formData.price_mode
|
if (formElements.price_mode) listingData.price_mode = formElements.price_mode
|
||||||
if (this.formData.category) listingData.category = this.formData.category
|
if (formElements.category) listingData.category = formElements.category
|
||||||
if (this.formData.condition) listingData.condition = this.formData.condition
|
if (formElements.condition) listingData.condition = formElements.condition
|
||||||
if (this.formData.shipping) listingData.shipping = this.formData.shipping
|
listingData.shipping = formElements.shipping
|
||||||
if (this.formData.moneroAddress) listingData.monero_address = this.formData.moneroAddress
|
if (formElements.moneroAddress) listingData.monero_address = formElements.moneroAddress
|
||||||
|
|
||||||
// Handle location - find or create in locations collection
|
// Handle location - find or create in locations collection
|
||||||
if (this.formData.locationData) {
|
if (this.formData.locationData) {
|
||||||
@@ -473,31 +550,45 @@ class PageCreate extends HTMLElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add images in junction table format
|
if (this.editMode) {
|
||||||
if (imageIds.length > 0) {
|
// Update existing listing
|
||||||
listingData.images = {
|
// Add new images if any
|
||||||
create: imageIds.map((id, index) => ({
|
if (newImageIds.length > 0) {
|
||||||
directus_files_id: id,
|
listingData.images = {
|
||||||
sort: index
|
create: newImageIds.map((id, index) => ({
|
||||||
}))
|
directus_files_id: id,
|
||||||
|
sort: this.existingImages.length + index
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await directus.updateListing(this.editId, listingData)
|
||||||
|
router.navigate(`/listing/${this.editId}`)
|
||||||
|
} else {
|
||||||
|
// Create new listing
|
||||||
|
if (newImageIds.length > 0) {
|
||||||
|
listingData.images = {
|
||||||
|
create: newImageIds.map((id, index) => ({
|
||||||
|
directus_files_id: id,
|
||||||
|
sort: index
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const listing = await directus.createListing(listingData)
|
||||||
|
this.clearDraft()
|
||||||
|
|
||||||
|
if (listing?.id) {
|
||||||
|
router.navigate(`/listing/${listing.id}`)
|
||||||
|
} else {
|
||||||
|
router.navigate('/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const listing = await directus.createListing(listingData)
|
|
||||||
|
|
||||||
this.clearDraft()
|
|
||||||
|
|
||||||
if (listing?.id) {
|
|
||||||
router.navigate(`/listing/${listing.id}`)
|
|
||||||
} else {
|
|
||||||
// Listing created but no ID returned - go to home
|
|
||||||
router.navigate('/')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to create listing:', error)
|
console.error('Failed to save listing:', error)
|
||||||
console.error('Error details:', JSON.stringify(error.data, null, 2))
|
console.error('Error details:', JSON.stringify(error.data, null, 2))
|
||||||
submitBtn.disabled = false
|
submitBtn.disabled = false
|
||||||
submitBtn.textContent = t('create.publish')
|
submitBtn.textContent = this.editMode ? t('create.saveChanges') : t('create.publish')
|
||||||
this.submitting = false
|
this.submitting = false
|
||||||
|
|
||||||
// Extract detailed error message
|
// Extract detailed error message
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { t, i18n } from '../../i18n.js'
|
import { t, i18n } from '../../i18n.js'
|
||||||
import { directus } from '../../services/directus.js'
|
import { directus } from '../../services/directus.js'
|
||||||
|
import { auth } from '../../services/auth.js'
|
||||||
import { listingsService } from '../../services/listings.js'
|
import { listingsService } from '../../services/listings.js'
|
||||||
import { getXmrRates, formatPrice as formatCurrencyPrice } from '../../services/currency.js'
|
import { getXmrRates, formatPrice as formatCurrencyPrice } from '../../services/currency.js'
|
||||||
import '../chat-widget.js'
|
import '../chat-widget.js'
|
||||||
@@ -14,6 +15,7 @@ class PageListing extends HTMLElement {
|
|||||||
this.loading = true
|
this.loading = true
|
||||||
this.isFavorite = false
|
this.isFavorite = false
|
||||||
this.rates = null
|
this.rates = null
|
||||||
|
this.isOwner = false
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@@ -33,10 +35,15 @@ class PageListing extends HTMLElement {
|
|||||||
this.rates = await getXmrRates()
|
this.rates = await getXmrRates()
|
||||||
this.loadFavoriteState()
|
this.loadFavoriteState()
|
||||||
|
|
||||||
// Increment view counter and update local state
|
// Check if current user is owner
|
||||||
const newViews = await directus.incrementListingViews(this.listingId)
|
await this.checkOwnership()
|
||||||
if (newViews !== null) {
|
|
||||||
this.listing.views = newViews
|
// Increment view counter only if not owner
|
||||||
|
if (!this.isOwner) {
|
||||||
|
const newViews = await directus.incrementListingViews(this.listingId)
|
||||||
|
if (newViews !== null) {
|
||||||
|
this.listing.views = newViews
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load other listings from same seller
|
// Load other listings from same seller
|
||||||
@@ -53,6 +60,20 @@ class PageListing extends HTMLElement {
|
|||||||
this.setupEventListeners()
|
this.setupEventListeners()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async checkOwnership() {
|
||||||
|
if (!auth.isLoggedIn() || !this.listing?.user_created) {
|
||||||
|
this.isOwner = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await auth.getUser()
|
||||||
|
this.isOwner = user?.id === this.listing.user_created
|
||||||
|
} catch (e) {
|
||||||
|
this.isOwner = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async loadSellerListings() {
|
async loadSellerListings() {
|
||||||
try {
|
try {
|
||||||
const response = await directus.getListings({
|
const response = await directus.getListings({
|
||||||
@@ -234,6 +255,34 @@ class PageListing extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderSidebar() {
|
renderSidebar() {
|
||||||
|
// Owner view: show edit button instead of contact
|
||||||
|
if (this.isOwner) {
|
||||||
|
return /* html */`
|
||||||
|
<div class="sidebar-card">
|
||||||
|
<a href="#/edit/${this.listingId}" class="btn btn-primary btn-lg sidebar-btn">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
||||||
|
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
|
||||||
|
</svg>
|
||||||
|
${t('listing.edit')}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="sidebar-actions">
|
||||||
|
<button class="action-btn" id="share-btn" title="${t('listing.share')}">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<circle cx="18" cy="5" r="3"></circle>
|
||||||
|
<circle cx="6" cy="12" r="3"></circle>
|
||||||
|
<circle cx="18" cy="19" r="3"></circle>
|
||||||
|
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line>
|
||||||
|
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line>
|
||||||
|
</svg>
|
||||||
|
<span>${t('listing.share')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
return /* html */`
|
return /* html */`
|
||||||
<div class="sidebar-card">
|
<div class="sidebar-card">
|
||||||
<button class="btn btn-primary btn-lg sidebar-btn" id="contact-btn">
|
<button class="btn btn-primary btn-lg sidebar-btn" id="contact-btn">
|
||||||
|
|||||||
@@ -139,7 +139,8 @@
|
|||||||
"viewPlural": "Aufrufe",
|
"viewPlural": "Aufrufe",
|
||||||
"share": "Teilen",
|
"share": "Teilen",
|
||||||
"report": "Melden",
|
"report": "Melden",
|
||||||
"moreFromSeller": "Weitere Anzeigen des Anbieters"
|
"moreFromSeller": "Weitere Anzeigen des Anbieters",
|
||||||
|
"edit": "Bearbeiten"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Nachricht senden",
|
"title": "Nachricht senden",
|
||||||
@@ -150,6 +151,7 @@
|
|||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Anzeige erstellen",
|
"title": "Anzeige erstellen",
|
||||||
|
"editTitle": "Anzeige bearbeiten",
|
||||||
"listingTitle": "Titel",
|
"listingTitle": "Titel",
|
||||||
"titlePlaceholder": "Was möchtest du verkaufen?",
|
"titlePlaceholder": "Was möchtest du verkaufen?",
|
||||||
"category": "Kategorie",
|
"category": "Kategorie",
|
||||||
@@ -180,6 +182,8 @@
|
|||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"publish": "Veröffentlichen",
|
"publish": "Veröffentlichen",
|
||||||
"publishing": "Wird veröffentlicht...",
|
"publishing": "Wird veröffentlicht...",
|
||||||
|
"saveChanges": "Änderungen speichern",
|
||||||
|
"saving": "Wird gespeichert...",
|
||||||
"publishFailed": "Veröffentlichung fehlgeschlagen. Bitte versuche es erneut.",
|
"publishFailed": "Veröffentlichung fehlgeschlagen. Bitte versuche es erneut.",
|
||||||
"invalidMoneroAddress": "Ungültige Monero-Adresse. Bitte prüfe das Format.",
|
"invalidMoneroAddress": "Ungültige Monero-Adresse. Bitte prüfe das Format.",
|
||||||
"draftRestored": "Entwurf wiederhergestellt",
|
"draftRestored": "Entwurf wiederhergestellt",
|
||||||
|
|||||||
@@ -139,7 +139,8 @@
|
|||||||
"viewPlural": "views",
|
"viewPlural": "views",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
"report": "Report",
|
"report": "Report",
|
||||||
"moreFromSeller": "More from this seller"
|
"moreFromSeller": "More from this seller",
|
||||||
|
"edit": "Edit"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Send Message",
|
"title": "Send Message",
|
||||||
@@ -150,6 +151,7 @@
|
|||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Create Listing",
|
"title": "Create Listing",
|
||||||
|
"editTitle": "Edit Listing",
|
||||||
"listingTitle": "Title",
|
"listingTitle": "Title",
|
||||||
"titlePlaceholder": "What do you want to sell?",
|
"titlePlaceholder": "What do you want to sell?",
|
||||||
"category": "Category",
|
"category": "Category",
|
||||||
@@ -180,6 +182,8 @@
|
|||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"publish": "Publish",
|
"publish": "Publish",
|
||||||
"publishing": "Publishing...",
|
"publishing": "Publishing...",
|
||||||
|
"saveChanges": "Save Changes",
|
||||||
|
"saving": "Saving...",
|
||||||
"publishFailed": "Publishing failed. Please try again.",
|
"publishFailed": "Publishing failed. Please try again.",
|
||||||
"invalidMoneroAddress": "Invalid Monero address. Please check the format.",
|
"invalidMoneroAddress": "Invalid Monero address. Please check the format.",
|
||||||
"draftRestored": "Draft restored",
|
"draftRestored": "Draft restored",
|
||||||
|
|||||||
@@ -139,7 +139,8 @@
|
|||||||
"viewPlural": "vues",
|
"viewPlural": "vues",
|
||||||
"share": "Partager",
|
"share": "Partager",
|
||||||
"report": "Signaler",
|
"report": "Signaler",
|
||||||
"moreFromSeller": "Autres annonces du vendeur"
|
"moreFromSeller": "Autres annonces du vendeur",
|
||||||
|
"edit": "Modifier"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"title": "Envoyer un message",
|
"title": "Envoyer un message",
|
||||||
@@ -150,6 +151,7 @@
|
|||||||
},
|
},
|
||||||
"create": {
|
"create": {
|
||||||
"title": "Créer une annonce",
|
"title": "Créer une annonce",
|
||||||
|
"editTitle": "Modifier l'annonce",
|
||||||
"listingTitle": "Titre",
|
"listingTitle": "Titre",
|
||||||
"titlePlaceholder": "Que voulez-vous vendre ?",
|
"titlePlaceholder": "Que voulez-vous vendre ?",
|
||||||
"category": "Catégorie",
|
"category": "Catégorie",
|
||||||
@@ -180,6 +182,8 @@
|
|||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"publish": "Publier",
|
"publish": "Publier",
|
||||||
"publishing": "Publication en cours...",
|
"publishing": "Publication en cours...",
|
||||||
|
"saveChanges": "Enregistrer les modifications",
|
||||||
|
"saving": "Enregistrement...",
|
||||||
"publishFailed": "La publication a échoué. Veuillez réessayer.",
|
"publishFailed": "La publication a échoué. Veuillez réessayer.",
|
||||||
"invalidMoneroAddress": "Adresse Monero invalide. Veuillez vérifier le format.",
|
"invalidMoneroAddress": "Adresse Monero invalide. Veuillez vérifier le format.",
|
||||||
"draftRestored": "Brouillon restauré",
|
"draftRestored": "Brouillon restauré",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const CACHE_NAME = 'dgray-v28';
|
const CACHE_NAME = 'dgray-v29';
|
||||||
const STATIC_ASSETS = [
|
const STATIC_ASSETS = [
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/index.html',
|
||||||
@@ -57,7 +57,13 @@ self.addEventListener('fetch', (event) => {
|
|||||||
|
|
||||||
if (request.method !== 'GET') return;
|
if (request.method !== 'GET') return;
|
||||||
|
|
||||||
// API calls: Network First
|
// API calls: Network First (external API domain)
|
||||||
|
if (url.hostname === 'api.dgray.io') {
|
||||||
|
event.respondWith(networkFirst(request));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy: API calls via path
|
||||||
if (url.pathname.includes('/api/')) {
|
if (url.pathname.includes('/api/')) {
|
||||||
event.respondWith(networkFirst(request));
|
event.respondWith(networkFirst(request));
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user