Guides image + draft gating, audit retention, ADR-015 gate, backup credential redaction
This commit is contained in:
@@ -142,7 +142,12 @@
|
||||
</div>
|
||||
|
||||
<div v-if="block.type === 'image'" class="ge-image">
|
||||
<img v-if="block.url" :src="block.url" alt="">
|
||||
<!-- Same authenticated fetch as the reader's view: an author must
|
||||
see the screenshot they just pasted. -->
|
||||
<img v-if="imageState(block).src" :src="imageState(block).src" alt="">
|
||||
<p v-else-if="imageState(block).failed" class="ge-imagefailed">
|
||||
Image unavailable - it may have been removed
|
||||
</p>
|
||||
<input v-model="block.caption" placeholder="Caption (optional)">
|
||||
<input v-model="block.alt" placeholder="Alt text, for anyone who cannot see it">
|
||||
</div>
|
||||
@@ -203,6 +208,7 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import GuideBlocks from '../components/GuideBlocks.vue'
|
||||
import { templates } from '../utils/templates.js'
|
||||
import { proof, applyFix } from '../utils/proof.js'
|
||||
import { useGuideImages } from '../utils/images.js'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -382,7 +388,10 @@ async function insertImage (file) {
|
||||
status.value = 'Uploading image...'
|
||||
try {
|
||||
const image = await uploadImage(file)
|
||||
const block = { id: newid(), type: 'image', url: image.url, caption: '', alt: '' }
|
||||
// imageid is what the renderer resolves; url stays for older readers and
|
||||
// for anything that still treats a block as a plain document.
|
||||
const block = { id: newid(), type: 'image', imageid: image.guideimageid,
|
||||
url: image.url, caption: '', alt: '' }
|
||||
blocks.value.splice(insertIndex(), 0, block)
|
||||
focused.value = block.id
|
||||
status.value = ''
|
||||
@@ -413,15 +422,21 @@ function onFilePick (event) {
|
||||
event.target.value = ''
|
||||
}
|
||||
|
||||
// Editor thumbnails resolve the same way the reader's do; see utils/images.js.
|
||||
const { state: imageState } = useGuideImages(blocks)
|
||||
|
||||
function payload () {
|
||||
return { title: guide.title, summary: guide.summary || '',
|
||||
tags: guide.tags || '', blocks: blocks.value }
|
||||
}
|
||||
|
||||
// Returns whether the save actually reached the server. publish() depends on
|
||||
// that answer: a publish that runs after a failed save promotes the LAST
|
||||
// revision the server accepted, which is not what is on the author's screen.
|
||||
async function save (quiet) {
|
||||
if (!guide.title.trim()) {
|
||||
if (!quiet) error.value = 'A guide needs a title before it can be saved'
|
||||
return
|
||||
return false
|
||||
}
|
||||
saving.value = true
|
||||
error.value = ''
|
||||
@@ -439,9 +454,11 @@ async function save (quiet) {
|
||||
dirty.value = false
|
||||
status.value = 'Saved ' + new Date().toLocaleTimeString()
|
||||
await loadRevisions()
|
||||
return true
|
||||
} catch (err) {
|
||||
error.value = apiError(err, 'Could not save this guide')
|
||||
status.value = 'Not saved'
|
||||
return false
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
@@ -476,7 +493,14 @@ async function publish () {
|
||||
+ 'anything flagged in a command. Publish anyway?')
|
||||
if (!ok) return
|
||||
}
|
||||
await save(true)
|
||||
// Publishing promotes the newest SAVED revision, so an unsaved edit is not
|
||||
// in what readers get. Reporting "Published version 4" over a failed save
|
||||
// tells the author their words went out when the old ones did.
|
||||
if (!await save(true)) {
|
||||
error.value = error.value
|
||||
|| 'Could not save this guide, so it was not published'
|
||||
return
|
||||
}
|
||||
try {
|
||||
const response = await api.post('/guides/' + guide.guideid + '/publish')
|
||||
guide.ispublished = true
|
||||
@@ -637,6 +661,8 @@ onMounted(async () => {
|
||||
background: transparent; color: var(--text-light); border-radius: 3px;
|
||||
cursor: pointer; justify-self: start;
|
||||
}
|
||||
.ge-imagefailed { margin: 0 0 0.35rem; padding: 0.5rem 0.7rem; font-size: 0.85rem;
|
||||
color: var(--danger, #b3261e); border: 1px dashed var(--border); border-radius: 4px; }
|
||||
.ge-image img { max-width: 18rem; display: block; margin-bottom: 0.35rem;
|
||||
border: 1px solid var(--border); border-radius: 4px; }
|
||||
.ge-image input { width: 100%; margin-bottom: 0.25rem; padding: 0.3rem 0.45rem;
|
||||
|
||||
Reference in New Issue
Block a user