fix: workspace — removed topbar buttons, unified modals, parent_id support
- Topbar: removed New File/New Folder buttons + 'Files' label - Breadcrumb: Workspace / work1 instead of work1 / Files - Modals: single shared create modal with focus auto, @keydown.enter - parent_id param: recursive folder/file creation inside folders - Sidebar buttons use same POST /api/local-workspace/items - newPageInWorkspace/newFolderInWorkspace now both use unified API
This commit is contained in:
@@ -545,14 +545,15 @@ async def create_local_workspace_item(request: Request):
|
|||||||
body = await request.json()
|
body = await request.json()
|
||||||
name = body.get("name", "Untitled").strip()
|
name = body.get("name", "Untitled").strip()
|
||||||
item_type = body.get("type", "page")
|
item_type = body.get("type", "page")
|
||||||
|
parent_id = body.get("parent_id")
|
||||||
ws = _get_active_workspace(request)
|
ws = _get_active_workspace(request)
|
||||||
ws_id = ws["id"] if ws else None
|
ws_id = ws["id"] if ws else None
|
||||||
section = 'Workspace' if item_type == 'folder' else 'Private'
|
section = 'Workspace' if item_type == 'folder' else 'Private'
|
||||||
with get_conn() as conn:
|
with get_conn() as conn:
|
||||||
cursor = conn.execute(
|
cursor = conn.execute(
|
||||||
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, parent_section) "
|
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, parent_section, parent_id) "
|
||||||
"VALUES ('', ?, ?, '', 'blocks', ?)",
|
"VALUES ('', ?, ?, '', 'blocks', ?, ?)",
|
||||||
(ws_id, name, section),
|
(ws_id, name, section, parent_id),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
pid = cursor.lastrowid
|
pid = cursor.lastrowid
|
||||||
|
|||||||
@@ -631,7 +631,7 @@
|
|||||||
.catch(err => { alert('Failed to create sub-page: ' + err.message); });
|
.catch(err => { alert('Failed to create sub-page: ' + err.message); });
|
||||||
},
|
},
|
||||||
newPageInWorkspace() {
|
newPageInWorkspace() {
|
||||||
const name = prompt('Page name:');
|
const name = prompt('File name:');
|
||||||
if (!name || !name.trim()) return;
|
if (!name || !name.trim()) return;
|
||||||
fetch('/api/local-workspace/items', {
|
fetch('/api/local-workspace/items', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -5,22 +5,19 @@
|
|||||||
{% block topbar %}
|
{% block topbar %}
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<div class="topbar-left">
|
<div class="topbar-left">
|
||||||
<span class="breadcrumb" style="color:rgba(255,255,255,.5)">{{ workspace_name }}</span>
|
<span class="breadcrumb" style="color:rgba(255,255,255,.5)">Workspace</span>
|
||||||
<span class="page-title-topbar">Files</span>
|
<span class="page-title-topbar">{{ workspace_name }}</span>
|
||||||
</div>
|
|
||||||
<div class="topbar-right">
|
|
||||||
<button class="page-action-btn" title="New File" @click="showFileModal=true">📄 New File</button>
|
|
||||||
<button class="page-action-btn" title="New Folder" @click="showFolderModal=true">📁 New Folder</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="topbar-right"></div>
|
||||||
</header>
|
</header>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<style>
|
<style>
|
||||||
.ws-layout{max-width:800px;margin:0 auto;padding:40px 24px;}
|
.ws-layout{max-width:800px;margin:0 auto;padding:40px 24px;}
|
||||||
.ws-header{margin-bottom:24px;}
|
.ws-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px;}
|
||||||
.ws-header h1{font-size:24px;margin-bottom:4px;}
|
.ws-header h1{font-size:24px;}
|
||||||
.ws-header p{color:var(--text-secondary);font-size:13px;}
|
.ws-toolbar{display:flex;gap:8px;}
|
||||||
|
|
||||||
.ws-item{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:8px;cursor:pointer;transition:background 150ms;border:1px solid transparent;}
|
.ws-item{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:8px;cursor:pointer;transition:background 150ms;border:1px solid transparent;}
|
||||||
.ws-item:hover{background:var(--bg-tertiary);border-color:var(--border);}
|
.ws-item:hover{background:var(--bg-tertiary);border-color:var(--border);}
|
||||||
@@ -38,7 +35,7 @@
|
|||||||
.empty-state p{font-size:13px;margin-bottom:24px;max-width:400px;margin-left:auto;margin-right:auto;}
|
.empty-state p{font-size:13px;margin-bottom:24px;max-width:400px;margin-left:auto;margin-right:auto;}
|
||||||
.empty-actions{display:flex;gap:10px;justify-content:center;}
|
.empty-actions{display:flex;gap:10px;justify-content:center;}
|
||||||
|
|
||||||
.btn{padding:10px 20px;border-radius:8px;font-size:13px;cursor:pointer;border:none;font-weight:500;transition:background 150ms;}
|
.btn{padding:10px 20px;border-radius:8px;font-size:13px;cursor:pointer;border:none;font-weight:500;}
|
||||||
.btn-primary{background:var(--accent);color:#fff;}
|
.btn-primary{background:var(--accent);color:#fff;}
|
||||||
.btn-primary:hover{background:var(--accent-hover);}
|
.btn-primary:hover{background:var(--accent-hover);}
|
||||||
.btn-secondary{background:var(--bg-tertiary);color:var(--text);border:1px solid var(--border);}
|
.btn-secondary{background:var(--bg-tertiary);color:var(--text);border:1px solid var(--border);}
|
||||||
@@ -47,15 +44,13 @@
|
|||||||
.btn-danger:hover{background:rgba(255,80,80,.2);}
|
.btn-danger:hover{background:rgba(255,80,80,.2);}
|
||||||
|
|
||||||
/* Modal */
|
/* Modal */
|
||||||
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:500;backdrop-filter:blur(4px);}
|
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:999;backdrop-filter:blur(4px);}
|
||||||
.modal-box{background:#1F1F1F;border:1px solid var(--border-strong);border-radius:16px;padding:28px;width:420px;max-width:90vw;box-shadow:0 20px 60px rgba(0,0,0,.5);}
|
.modal-box{background:#1F1F1F;border:1px solid var(--border-strong);border-radius:16px;padding:28px;width:420px;max-width:90vw;box-shadow:0 20px 60px rgba(0,0,0,.5);}
|
||||||
.modal-box h3{font-size:16px;margin-bottom:6px;display:flex;align-items:center;gap:8px;}
|
.modal-box h3{font-size:16px;margin-bottom:6px;display:flex;align-items:center;gap:8px;}
|
||||||
.modal-box .modal-sub{font-size:12px;color:var(--text-tertiary);margin-bottom:20px;}
|
.modal-box .modal-sub{font-size:12px;color:var(--text-tertiary);margin-bottom:20px;}
|
||||||
.modal-input{width:100%;padding:12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:14px;outline:none;}
|
.modal-input{width:100%;padding:12px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:10px;color:var(--text);font-size:14px;outline:none;}
|
||||||
.modal-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(35,131,226,.15);}
|
.modal-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(35,131,226,.15);}
|
||||||
.modal-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:20px;}
|
.modal-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:20px;}
|
||||||
|
|
||||||
/* Delete confirm */
|
|
||||||
.delete-confirm{padding:12px;background:rgba(255,80,80,.08);border-radius:8px;margin-top:12px;font-size:13px;color:var(--text-secondary);}
|
.delete-confirm{padding:12px;background:rgba(255,80,80,.08);border-radius:8px;margin-top:12px;font-size:13px;color:var(--text-secondary);}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -63,25 +58,26 @@
|
|||||||
|
|
||||||
<div class="ws-header">
|
<div class="ws-header">
|
||||||
<h1>📁 {{ workspace_name }}</h1>
|
<h1>📁 {{ workspace_name }}</h1>
|
||||||
<p x-text="items.length+' item(s)'">{{ items | length }} item(s)</p>
|
<div class="ws-toolbar">
|
||||||
|
<button class="btn btn-primary" @click="openCreateModal('file')">📄 New File</button>
|
||||||
|
<button class="btn btn-secondary" @click="openCreateModal('folder')">📁 New Folder</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- File/Folder list -->
|
<!-- File/Folder list -->
|
||||||
<template x-if="items.length > 0">
|
<div x-show="items.length">
|
||||||
<div>
|
<template x-for="item in items" :key="item.id">
|
||||||
<template x-for="item in items" :key="item.id">
|
<div class="ws-item">
|
||||||
<div class="ws-item">
|
<span class="icon" x-text="item.icon"></span>
|
||||||
<span class="icon" x-text="item.icon||'📄'"></span>
|
<span class="name" x-text="item.name" @dblclick="rename(item)"></span>
|
||||||
<span class="name" x-text="item.name" @dblclick="rename(item)"></span>
|
<div class="actions">
|
||||||
<div class="actions">
|
<button class="ws-act" @click="openItem(item)" title="Open">Open</button>
|
||||||
<button class="ws-act" @click="openItem(item)" title="Open">Open</button>
|
<button class="ws-act" @click="rename(item)" title="Rename">✏️</button>
|
||||||
<button class="ws-act" @click="rename(item)" title="Rename">✏️</button>
|
<button class="ws-act danger" @click="confirmDelete(item)" title="Delete">🗑</button>
|
||||||
<button class="ws-act danger" @click="confirmDelete(item)" title="Delete">🗑</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
|
|
||||||
<!-- Empty state -->
|
<!-- Empty state -->
|
||||||
<div class="empty-state" x-show="!items.length">
|
<div class="empty-state" x-show="!items.length">
|
||||||
@@ -89,33 +85,26 @@
|
|||||||
<h2>Empty workspace</h2>
|
<h2>Empty workspace</h2>
|
||||||
<p>Create a new file to start writing, or a folder to organize your content</p>
|
<p>Create a new file to start writing, or a folder to organize your content</p>
|
||||||
<div class="empty-actions">
|
<div class="empty-actions">
|
||||||
<button class="btn btn-primary" @click="showFileModal=true">📄 New File</button>
|
<button class="btn btn-primary" @click="openCreateModal('file')">📄 New File</button>
|
||||||
<button class="btn btn-secondary" @click="showFolderModal=true">📁 New Folder</button>
|
<button class="btn btn-secondary" @click="openCreateModal('folder')">📁 New Folder</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Create File Modal -->
|
<!-- Create Modal (shared for file and folder) -->
|
||||||
<div class="modal-overlay" x-show="showFileModal" @click.away="showFileModal=false" @keydown.escape="showFileModal=false">
|
<div class="modal-overlay" x-show="showCreateModal" @click.away="showCreateModal=false" @keydown.escape="showCreateModal=false">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
<h3>📄 New File</h3>
|
<h3>
|
||||||
<p class="modal-sub">Create a new document in <strong>{{ workspace_name }}</strong></p>
|
<span x-text="createType==='folder'?'📁':'📄'"></span>
|
||||||
<input class="modal-input" x-model="newName" placeholder="File name" @keydown.enter="createFile" x-ref="fileInput" x-init="$watch('showFileModal',v=>{if(v)setTimeout(()=>$refs.fileInput.focus(),100)})">
|
<span x-text="createType==='folder'?'New Folder':'New File'"></span>
|
||||||
|
</h3>
|
||||||
|
<p class="modal-sub">
|
||||||
|
Create in <strong>{{ workspace_name }}</strong>
|
||||||
|
<span x-show="parentFolder"> / <span x-text="parentFolder.name"></span></span>
|
||||||
|
</p>
|
||||||
|
<input class="modal-input" x-model="newName" :placeholder="createType==='folder'?'Folder name':'File name'" @keydown.enter="doCreate" x-ref="createInput">
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="btn btn-secondary" @click="showFileModal=false">Cancel</button>
|
<button class="btn btn-secondary" @click="showCreateModal=false">Cancel</button>
|
||||||
<button class="btn btn-primary" @click="createFile" :disabled="!newName.trim()">Create File</button>
|
<button class="btn btn-primary" @click="doCreate" :disabled="!newName.trim()">Create</button>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Create Folder Modal -->
|
|
||||||
<div class="modal-overlay" x-show="showFolderModal" @click.away="showFolderModal=false" @keydown.escape="showFolderModal=false">
|
|
||||||
<div class="modal-box">
|
|
||||||
<h3>📁 New Folder</h3>
|
|
||||||
<p class="modal-sub">Create a new folder in <strong>{{ workspace_name }}</strong></p>
|
|
||||||
<input class="modal-input" x-model="newName" placeholder="Folder name" @keydown.enter="createFolder" x-ref="folderInput" x-init="$watch('showFolderModal',v=>{if(v)setTimeout(()=>$refs.folderInput.focus(),100)})">
|
|
||||||
<div class="modal-actions">
|
|
||||||
<button class="btn btn-secondary" @click="showFolderModal=false">Cancel</button>
|
|
||||||
<button class="btn btn-primary" @click="createFolder" :disabled="!newName.trim()">Create Folder</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,7 +114,7 @@
|
|||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
<h3>✏️ Rename</h3>
|
<h3>✏️ Rename</h3>
|
||||||
<p class="modal-sub">Rename <strong x-text="renameTarget?.name"></strong></p>
|
<p class="modal-sub">Rename <strong x-text="renameTarget?.name"></strong></p>
|
||||||
<input class="modal-input" x-model="newName" placeholder="New name" @keydown.enter="doRename" x-ref="renameInput" x-init="$watch('showRenameModal',v=>{if(v)setTimeout(()=>$refs.renameInput.focus(),100)})">
|
<input class="modal-input" x-model="newName" @keydown.enter="doRename" x-ref="renameInput">
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="btn btn-secondary" @click="showRenameModal=false">Cancel</button>
|
<button class="btn btn-secondary" @click="showRenameModal=false">Cancel</button>
|
||||||
<button class="btn btn-primary" @click="doRename">Rename</button>
|
<button class="btn btn-primary" @click="doRename">Rename</button>
|
||||||
@@ -137,8 +126,8 @@
|
|||||||
<div class="modal-overlay" x-show="showDeleteModal" @click.away="showDeleteModal=false" @keydown.escape="showDeleteModal=false">
|
<div class="modal-overlay" x-show="showDeleteModal" @click.away="showDeleteModal=false" @keydown.escape="showDeleteModal=false">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
<h3>🗑 Delete</h3>
|
<h3>🗑 Delete</h3>
|
||||||
<p class="modal-sub">Are you sure you want to delete <strong x-text="deleteTarget?.name"></strong>?</p>
|
<p class="modal-sub">Delete <strong x-text="deleteTarget?.name"></strong>?</p>
|
||||||
<div class="delete-confirm">This action cannot be undone. All content will be permanently deleted.</div>
|
<div class="delete-confirm">This cannot be undone. All content will be permanently deleted.</div>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="btn btn-secondary" @click="showDeleteModal=false">Cancel</button>
|
<button class="btn btn-secondary" @click="showDeleteModal=false">Cancel</button>
|
||||||
<button class="btn btn-danger" @click="doDelete">Delete</button>
|
<button class="btn btn-danger" @click="doDelete">Delete</button>
|
||||||
@@ -152,83 +141,97 @@
|
|||||||
function wsPage() {
|
function wsPage() {
|
||||||
return {
|
return {
|
||||||
items: [],
|
items: [],
|
||||||
showFileModal: false,
|
showCreateModal: false,
|
||||||
showFolderModal: false,
|
|
||||||
showRenameModal: false,
|
showRenameModal: false,
|
||||||
showDeleteModal: false,
|
showDeleteModal: false,
|
||||||
|
createType: 'file',
|
||||||
newName: '',
|
newName: '',
|
||||||
|
parentFolder: null,
|
||||||
renameTarget: null,
|
renameTarget: null,
|
||||||
deleteTarget: null,
|
deleteTarget: null,
|
||||||
|
|
||||||
async init() {
|
async init() { await this.load(); },
|
||||||
await this.load();
|
|
||||||
},
|
|
||||||
async load() {
|
async load() {
|
||||||
try {
|
try {
|
||||||
const r = await fetch('/api/local-workspace/tree');
|
const r = await fetch('/api/local-workspace/tree');
|
||||||
const data = await r.json();
|
const data = await r.json();
|
||||||
// Flatten tree for display
|
|
||||||
const flat = [];
|
const flat = [];
|
||||||
for (const node of (data.tree || [])) {
|
for (const node of (data.tree || [])) {
|
||||||
flat.push({
|
flat.push({id:node.id, name:node.name, icon:node.type==='folder'?'📁':'📄', type:node.type, parentId:null});
|
||||||
id: node.id, name: node.name,
|
|
||||||
icon: node.type === 'folder' ? '📁' : (node.icon || '📄'),
|
|
||||||
type: node.type || 'page'
|
|
||||||
});
|
|
||||||
for (const child of (node.children || [])) {
|
for (const child of (node.children || [])) {
|
||||||
flat.push({
|
flat.push({id:child.id, name:' '+child.name, icon:'📄', type:'page', parentId:node.id});
|
||||||
id: child.id, name: ' ' + child.name,
|
|
||||||
icon: child.type === 'folder' ? '📁' : '📄',
|
|
||||||
type: child.type || 'page'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.items = flat;
|
this.items = flat;
|
||||||
} catch(e) { console.error(e); }
|
} catch(e) { console.error(e); }
|
||||||
},
|
},
|
||||||
async createFile() {
|
|
||||||
if (!this.newName.trim()) return;
|
openCreateModal(type, parent) {
|
||||||
await fetch('/api/local-workspace/items', {
|
this.createType = type;
|
||||||
method:'POST', headers:{'Content-Type':'application/json'},
|
this.parentFolder = parent || null;
|
||||||
body:JSON.stringify({name:this.newName.trim(), type:'page'})
|
this.newName = '';
|
||||||
|
this.showCreateModal = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const el = this.$refs.createInput;
|
||||||
|
if (el) setTimeout(() => el.focus(), 100);
|
||||||
});
|
});
|
||||||
this.newName = ''; this.showFileModal = false;
|
|
||||||
await this.load();
|
|
||||||
},
|
},
|
||||||
async createFolder() {
|
|
||||||
|
async doCreate() {
|
||||||
if (!this.newName.trim()) return;
|
if (!this.newName.trim()) return;
|
||||||
|
const body = { name: this.newName.trim(), type: this.createType };
|
||||||
|
if (this.parentFolder) body.parent_id = this.parentFolder.id;
|
||||||
const r = await fetch('/api/local-workspace/items', {
|
const r = await fetch('/api/local-workspace/items', {
|
||||||
method:'POST', headers:{'Content-Type':'application/json'},
|
method:'POST', headers:{'Content-Type':'application/json'},
|
||||||
body:JSON.stringify({name:this.newName.trim(), type:'folder'})
|
body: JSON.stringify(body)
|
||||||
});
|
});
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
this.newName = ''; this.showFolderModal = false;
|
this.newName = ''; this.showCreateModal = false; this.parentFolder = null;
|
||||||
await this.load();
|
await this.load();
|
||||||
} else {
|
} else {
|
||||||
alert('Failed to create folder');
|
const e = await r.json();
|
||||||
|
alert(e.error || 'Failed to create');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async createFileOrFolder(type, name) {
|
||||||
|
// Used by sidebar buttons
|
||||||
|
return await fetch('/api/local-workspace/items', {
|
||||||
|
method:'POST', headers:{'Content-Type':'application/json'},
|
||||||
|
body: JSON.stringify({ name, type })
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
openItem(item) {
|
openItem(item) {
|
||||||
window.location = `/pages/${item.id}`;
|
window.location = `/pages/${item.id}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
rename(item) {
|
rename(item) {
|
||||||
this.renameTarget = item;
|
this.renameTarget = item;
|
||||||
this.newName = item.name.trim();
|
this.newName = item.name.trim();
|
||||||
this.showRenameModal = true;
|
this.showRenameModal = true;
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const el = this.$refs.renameInput;
|
||||||
|
if (el) setTimeout(() => el.focus(), 100);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async doRename() {
|
async doRename() {
|
||||||
if (!this.newName.trim() || !this.renameTarget) return;
|
if (!this.newName.trim() || !this.renameTarget) return;
|
||||||
await fetch(`/api/local-workspace/items/${this.renameTarget.id}`, {
|
await fetch(`/api/local-workspace/items/${this.renameTarget.id}`, {
|
||||||
method:'PUT', headers:{'Content-Type':'application/json'},
|
method:'PUT', headers:{'Content-Type':'application/json'},
|
||||||
body:JSON.stringify({name:this.newName.trim()})
|
body: JSON.stringify({name:this.newName.trim()})
|
||||||
});
|
});
|
||||||
this.newName = ''; this.showRenameModal = false; this.renameTarget = null;
|
this.showRenameModal = false; this.renameTarget = null; this.newName = '';
|
||||||
await this.load();
|
await this.load();
|
||||||
},
|
},
|
||||||
|
|
||||||
confirmDelete(item) {
|
confirmDelete(item) {
|
||||||
this.deleteTarget = item;
|
this.deleteTarget = item;
|
||||||
this.showDeleteModal = true;
|
this.showDeleteModal = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
async doDelete() {
|
async doDelete() {
|
||||||
if (!this.deleteTarget) return;
|
if (!this.deleteTarget) return;
|
||||||
await fetch(`/api/local-workspace/items/${this.deleteTarget.id}`, {method:'DELETE'});
|
await fetch(`/api/local-workspace/items/${this.deleteTarget.id}`, {method:'DELETE'});
|
||||||
|
|||||||
Reference in New Issue
Block a user