Compare commits
8 changed files with 347 additions and 169 deletions
|
|
@ -168,7 +168,7 @@ func render(b *strings.Builder, nodes []Node) {
|
||||||
b.WriteString("</figure>")
|
b.WriteString("</figure>")
|
||||||
|
|
||||||
case *Audio:
|
case *Audio:
|
||||||
b.WriteString(`<audio controls="" preload="metadata" src="/uploaded/`)
|
b.WriteString(`<audio controls="" preload="metadata" src="`)
|
||||||
b.WriteString(html.EscapeString(safeUrl(v.URL)))
|
b.WriteString(html.EscapeString(safeUrl(v.URL)))
|
||||||
b.WriteString(`" type="audio/mpeg"></audio>`)
|
b.WriteString(`" type="audio/mpeg"></audio>`)
|
||||||
case *Aside:
|
case *Aside:
|
||||||
|
|
|
||||||
|
|
@ -326,72 +326,99 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||||
|
align-items: start;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
position: relative;
|
||||||
form {
|
form {
|
||||||
max-width: unset;
|
max-width: unset;
|
||||||
flex-basis: 50%;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
article {
|
|
||||||
flex-basis: 50%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
}
|
|
||||||
textarea {
|
textarea {
|
||||||
max-width: unset;
|
max-width: unset;
|
||||||
height: 80vh;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-preview-container {
|
||||||
|
position: sticky;
|
||||||
|
top: var(--margin-s);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-preview-btn {
|
||||||
|
position: static;
|
||||||
|
margin: var(--margin-s) 0;
|
||||||
|
}
|
||||||
|
|
||||||
.media-picker {
|
.media-picker {
|
||||||
padding-left:0;
|
display: none;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
max-height: 50vh;
|
height: 100%;
|
||||||
> *+* {
|
max-height: 60vh;
|
||||||
margin-top: 1em;
|
border: 1px solid var(--border);
|
||||||
width: 100%;
|
border-radius: var(--radius-sm);
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
padding:0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-picker__media {
|
.media-picker__media {
|
||||||
display: grid;
|
aspect-ratio: 1/1;
|
||||||
grid-template-columns: 150px 1fr;
|
position: relative;
|
||||||
gap:1em;
|
|
||||||
line-height: 1;
|
|
||||||
img,
|
img,
|
||||||
audio,
|
|
||||||
video {
|
video {
|
||||||
width: 150px;
|
width: 100%;
|
||||||
height: auto;
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-picker__buttons {
|
.media-picker__buttons {
|
||||||
|
visibility: hidden;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-top:1em;
|
flex-direction: column;
|
||||||
gap: 0.5em;
|
position: absolute;
|
||||||
align-items: flex-start;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
inset: 0;
|
||||||
|
gap: 6px;
|
||||||
|
button {
|
||||||
|
flex: 0 1 32px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[theme="raw"] .media-picker__buttons {
|
.media-picker__media:hover .media-picker__buttons {
|
||||||
display: none;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-preview {
|
.page-preview {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
padding: var(--padding-form);
|
padding: var(--padding-form);
|
||||||
overflow-y: scroll;
|
display: block;
|
||||||
max-height: 100vh;
|
|
||||||
svg {
|
svg {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-preview-container[data-show="false"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-preview-container[data-show="gallery"] {
|
||||||
|
.media-picker {
|
||||||
|
display: grid;
|
||||||
|
}
|
||||||
|
.page-preview {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-preview-container[data-show="preview"] {
|
||||||
|
.media-picker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.page-preview {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.edit-footer {
|
.edit-footer {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,17 @@
|
||||||
<main>
|
<main>
|
||||||
<h1>New page</h1>
|
<h1>New page</h1>
|
||||||
<div class="edit-footer">
|
<div class="edit-footer">
|
||||||
|
<div>
|
||||||
|
<button data-small data-secondary onClick="toggleGallery()" id="mediasBtn">Medias</button>
|
||||||
|
<button data-small onClick="togglePreview()" id="previewBtn">Preview</button>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<form data-naked action='/files/upload' method='POST' class="media-form">
|
<form data-naked action='/files/upload' method='POST' class="media-form">
|
||||||
<div>
|
<div>
|
||||||
<input type="file" multiple="multiple" id="fileUpload" value="Upload file" />
|
<input type="file" multiple="multiple" id="fileUpload" value="Upload file" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button data-small type='submit' data-size="20" id="uploadMediaBtn" >
|
<button data-small type='submit' data-size="20" id="uploadMediaBtn" disabled>
|
||||||
Upload file
|
Upload file
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -19,15 +23,6 @@
|
||||||
<div class="edit-page">
|
<div class="edit-page">
|
||||||
<form action='/page/create' method='POST'>
|
<form action='/page/create' method='POST'>
|
||||||
<input type='hidden' id="csrf_token" name='csrf_token' value='{{.CSRFToken}}'>
|
<input type='hidden' id="csrf_token" name='csrf_token' value='{{.CSRFToken}}'>
|
||||||
|
|
||||||
<label for="bodyError">Content:</label>
|
|
||||||
<textarea id="body" name='body' {{with .Form.FieldErrors.body}} aria-invalid aria-describedby="bodyError" {{end}}>{{.Form.Body}}</textarea>
|
|
||||||
{{with .Form.FieldErrors.body}}
|
|
||||||
<span id="summaryError">{{.}}</span>
|
|
||||||
{{end}}
|
|
||||||
{{template "mediaselector" .}}
|
|
||||||
|
|
||||||
|
|
||||||
<label for="name">Title:</label>
|
<label for="name">Title:</label>
|
||||||
<input autofocus type='text' id="name" name='name' value="{{.Form.Name}}" {{with .Form.FieldErrors.name}} aria-invalid
|
<input autofocus type='text' id="name" name='name' value="{{.Form.Name}}" {{with .Form.FieldErrors.name}} aria-invalid
|
||||||
aria-describedby="nameError" {{end}}>
|
aria-describedby="nameError" {{end}}>
|
||||||
|
|
@ -42,6 +37,11 @@
|
||||||
<span id="summaryError">{{.}}</span>
|
<span id="summaryError">{{.}}</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<label for="bodyError">Content:</label>
|
||||||
|
<textarea id="body" name='body' {{with .Form.FieldErrors.body}} aria-invalid aria-describedby="bodyError" {{end}}>{{.Form.Body}}</textarea>
|
||||||
|
{{with .Form.FieldErrors.body}}
|
||||||
|
<span id="summaryError">{{.}}</span>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<div class="grouped">
|
<div class="grouped">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -111,11 +111,50 @@
|
||||||
<input type='submit' value='Publish page' data-size="20">
|
<input type='submit' value='Publish page' data-size="20">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="media-preview-container" data-show="preview" >
|
||||||
|
|
||||||
|
<div class="media-picker">
|
||||||
|
{{range $image := .Files}}
|
||||||
|
{{if eq $image.Extension ".png" ".jpg" ".jpeg" ".gif" ".webp" ".avif"}}
|
||||||
|
<div class="media-picker__media" id="{{$image.Filename}}">
|
||||||
|
<img loading="lazy" src="{{$image.Slug}}" />
|
||||||
|
<div class="media-picker__buttons">
|
||||||
|
<button data-small onClick="insertMedia({{$image.Filename}}+{{$image.Extension}}, {{$image.Extension}}, true)">Insert Image</button>
|
||||||
|
<button data-small data-secondary
|
||||||
|
onClick="deleteMedia({{$image.Filename}}+{{$image.Extension}})">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $image.Extension ".mp4"}}
|
||||||
|
<div class="media-picker__media">
|
||||||
|
<video src="{{$image.Slug}}" ></video>
|
||||||
|
<div class="media-picker__buttons">
|
||||||
|
<button data-small onClick="insertMedia({{$image.Filename}}+{{$image.Extension}}, {{$image.Extension}}, true)">Insert Video</button>
|
||||||
|
<button data-small data-secondary
|
||||||
|
onClick="deleteMedia({{$image.Filename}}+{{$image.Extension}})">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $image.Extension ".mp3"}}
|
||||||
|
<div class="media-picker__media">
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<strong>{{$image.Filename}}{{$image.Extension}}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="media-picker__buttons">
|
||||||
|
<button data-small onClick="insertMedia({{$image.Filename}}+{{$image.Extension}}, {{$image.Extension}}, true)">Insert Aduio</button>
|
||||||
|
<button data-small data-secondary
|
||||||
|
onClick="deleteMedia({{$image.Filename}}+{{$image.Extension}})">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
<article class="page-preview {{.Form.Category}}"></article>
|
<article class="page-preview {{.Form.Category}}"></article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
|
autosize(document.querySelector('textarea'));
|
||||||
|
|
||||||
const formated = document.querySelector('article');
|
const formated = document.querySelector('article');
|
||||||
const textarea = document.querySelector('textarea');
|
const textarea = document.querySelector('textarea');
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
<main>
|
<main>
|
||||||
<h1>Edit page</h1>
|
<h1>Edit page</h1>
|
||||||
<div class="edit-footer">
|
<div class="edit-footer">
|
||||||
|
<div>
|
||||||
|
<button data-small data-secondary onClick="toggleGallery()" id="mediasBtn">Medias</button>
|
||||||
|
<button data-small onClick="togglePreview()" id="previewBtn">Preview</button>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<form data-naked action='/files/upload' method='POST' class="media-form">
|
<form data-naked action='/files/upload' method='POST' class="media-form">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -22,17 +26,11 @@
|
||||||
<input type="hidden" value="{{.Form.Id}}" name="id"/>
|
<input type="hidden" value="{{.Form.Id}}" name="id"/>
|
||||||
<input type="hidden" value="{{.Form.Slug}}" name="slug"/>
|
<input type="hidden" value="{{.Form.Slug}}" name="slug"/>
|
||||||
|
|
||||||
|
<img src="/og/{{.Form.Slug}}" />
|
||||||
<label for="bodyError">Content:</label>
|
|
||||||
<textarea autofocus id="body" name='body' {{with .Form.FieldErrors.body}} aria-invalid aria-describedby="bodyError" {{end}}>{{.Form.Body}}</textarea>
|
|
||||||
{{with .Form.FieldErrors.body}}
|
|
||||||
<span id="summaryError">{{.}}</span>
|
|
||||||
{{end}}
|
|
||||||
{{template "mediaselector" .}}
|
|
||||||
|
|
||||||
<label for="name">Title:</label>
|
<label for="name">Title:</label>
|
||||||
<span>Changing the title will also change the page url</span>
|
<span>Changing the title will also change the page url</span>
|
||||||
<input type='text' id="name" name='name' value="{{.Form.Name}}" {{with .Form.FieldErrors.name}} aria-invalid
|
<input autofocus type='text' id="name" name='name' value="{{.Form.Name}}" {{with .Form.FieldErrors.name}} aria-invalid
|
||||||
aria-describedby="nameError" {{end}}>
|
aria-describedby="nameError" {{end}}>
|
||||||
{{with .Form.FieldErrors.name}}
|
{{with .Form.FieldErrors.name}}
|
||||||
<span id="nameError">{{.}}</span>
|
<span id="nameError">{{.}}</span>
|
||||||
|
|
@ -45,6 +43,14 @@
|
||||||
<span id="summaryError">{{.}}</span>
|
<span id="summaryError">{{.}}</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<label for="bodyError">Content:</label>
|
||||||
|
<textarea id="body" name='body' {{with .Form.FieldErrors.body}} aria-invalid aria-describedby="bodyError" {{end}}>{{.Form.Body}}</textarea>
|
||||||
|
{{with .Form.FieldErrors.body}}
|
||||||
|
<span id="summaryError">{{.}}</span>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="grouped">
|
<div class="grouped">
|
||||||
<div>
|
<div>
|
||||||
{{ $host := .Form.Host }}
|
{{ $host := .Form.Host }}
|
||||||
|
|
@ -108,21 +114,54 @@
|
||||||
<label for="menu">Show Sub Pages List</label>
|
<label for="menu">Show Sub Pages List</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div>
|
|
||||||
<details>
|
|
||||||
<summary>OG Image Preview</summary>
|
|
||||||
<img src="/og/{{.Form.Slug}}" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button type='submit' data-size="20">Save changes</button>
|
<button type='submit' data-size="20">Save changes</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
<div class="media-preview-container" data-show="preview" >
|
||||||
|
|
||||||
|
<div class="media-picker">
|
||||||
|
{{range $image := .Files}}
|
||||||
|
{{if eq $image.Extension ".png" ".jpg" ".jpeg" ".gif" ".webp" ".avif"}}
|
||||||
|
<div class="media-picker__media" id="{{$image.Filename}}">
|
||||||
|
<img loading="lazy" src="{{$image.Slug}}" />
|
||||||
|
<div class="media-picker__buttons">
|
||||||
|
<button data-small onClick="insertMedia({{$image.Filename}}+{{$image.Extension}}, {{$image.Extension}}, true)">Insert Image</button>
|
||||||
|
<button data-small data-secondary
|
||||||
|
onClick="deleteMedia({{$image.Filename}}+{{$image.Extension}})">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $image.Extension ".mp4"}}
|
||||||
|
<div class="media-picker__media">
|
||||||
|
<video src="{{$image.Slug}}" ></video>
|
||||||
|
<div class="media-picker__buttons">
|
||||||
|
<button data-small onClick="insertMedia({{$image.Filename}}+{{$image.Extension}}, {{$image.Extension}}, true)">Insert Video</button>
|
||||||
|
<button data-small data-secondary
|
||||||
|
onClick="deleteMedia({{$image.Filename}}+{{$image.Extension}})">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if eq $image.Extension ".mp3"}}
|
||||||
|
<div class="media-picker__media">
|
||||||
|
<div style="text-align: center;">
|
||||||
|
<strong>{{$image.Filename}}{{$image.Extension}}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="media-picker__buttons">
|
||||||
|
<button data-small onClick="insertMedia({{$image.Filename}}+{{$image.Extension}}, {{$image.Extension}}, true)">Insert Audio</button>
|
||||||
|
<button data-small data-secondary
|
||||||
|
onClick="deleteMedia({{$image.Filename}}+{{$image.Extension}})">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
<article class="page-preview {{.Form.Category}}"></article>
|
<article class="page-preview {{.Form.Category}}"></article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<script>
|
<script>
|
||||||
|
autosize(document.querySelector('textarea'));
|
||||||
|
|
||||||
const formated = document.querySelector('article');
|
const formated = document.querySelector('article');
|
||||||
const textarea = document.querySelector('textarea');
|
const textarea = document.querySelector('textarea');
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
{{define "mediaselector"}}
|
|
||||||
<div>
|
|
||||||
<details>
|
|
||||||
<summary>Medias</summary>
|
|
||||||
<ul role="list" class="media-picker">
|
|
||||||
{{range $image := .Files}}
|
|
||||||
{{if eq $image.Extension ".png" ".jpg" ".jpeg" ".gif" ".webp" ".avif"}}
|
|
||||||
<li class="media-picker__media" id="{{$image.Filename}}">
|
|
||||||
<img loading="lazy" src="{{$image.Slug}}" />
|
|
||||||
<span>
|
|
||||||
<small><strong>{{$image.Filename}}{{$image.Extension}}</strong></small><br>
|
|
||||||
<small><code>(image: {{$image.Filename}}{{$image.Extension}} alt: )</code></small>
|
|
||||||
<div class="media-picker__buttons">
|
|
||||||
<button data-small onClick="insertMedia({{$image.Filename}}+{{$image.Extension}}, {{$image.Extension}}, true, event)">Insert</button>
|
|
||||||
<button data-small data-secondary
|
|
||||||
onClick="deleteMedia({{$image.Filename}}+{{$image.Extension}}, event)">Delete</button>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{if eq $image.Extension ".mp4"}}
|
|
||||||
<li class="media-picker__media" id="{{$image.Filename}}">
|
|
||||||
<video controls src="{{$image.Slug}}" ></video>
|
|
||||||
<span>
|
|
||||||
<small><strong>{{$image.Filename}}{{$image.Extension}}</strong></small><br>
|
|
||||||
<small><code>(video: {{$image.Filename}}{{$image.Extension}})</code></small>
|
|
||||||
<div class="media-picker__buttons">
|
|
||||||
<button data-small onClick="insertMedia({{$image.Filename}}+{{$image.Extension}}, {{$image.Extension}}, true, event)">Insert Video</button>
|
|
||||||
<button data-small data-secondary
|
|
||||||
onClick="deleteMedia({{$image.Filename}}+{{$image.Extension}}, event)">Delete</button>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{if eq $image.Extension ".mp3"}}
|
|
||||||
<li class="media-picker__media" id="{{$image.Filename}}">
|
|
||||||
<audio controls src="{{$image.Slug}}"></audio>
|
|
||||||
<span>
|
|
||||||
<small><strong>{{$image.Filename}}{{$image.Extension}}</strong></small><br>
|
|
||||||
<small><code>(audio: {{$image.Filename}}{{$image.Extension}})</code></small>
|
|
||||||
<div class="media-picker__buttons">
|
|
||||||
<button data-small onClick="insertMedia({{$image.Filename}}+{{$image.Extension}}, {{$image.Extension}}, true, event)">Insert audio</button>
|
|
||||||
<button data-small data-secondary
|
|
||||||
onClick="deleteMedia({{$image.Filename}}+{{$image.Extension}}, event)">Delete</button>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
|
|
@ -326,16 +326,58 @@ background: lightgrey;
|
||||||
}
|
}
|
||||||
textarea {
|
textarea {
|
||||||
max-width: unset;
|
max-width: unset;
|
||||||
width:100%;
|
|
||||||
height:30vh;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-preview-container {
|
||||||
|
position: sticky;
|
||||||
|
top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.toggle-preview-btn {
|
.toggle-preview-btn {
|
||||||
position: static;
|
position: static;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-picker {
|
||||||
|
display: none;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||||
|
overflow-y: scroll;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 60vh;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-picker__media {
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
position: relative;
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-picker__buttons {
|
||||||
|
visibility: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
inset: 0;
|
||||||
|
gap: 6px;
|
||||||
|
button {
|
||||||
|
flex: 0 1 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-picker__media:hover .media-picker__buttons {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.page-preview {
|
.page-preview {
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
|
|
@ -347,56 +389,40 @@ background: lightgrey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media-preview-container[data-show="false"] {
|
||||||
.media-picker {
|
|
||||||
padding-left:0;
|
|
||||||
overflow-y: scroll;
|
|
||||||
max-height: 50vh;
|
|
||||||
> *+* {
|
|
||||||
margin-top: 1em;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
padding:0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.media-picker__media {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 150px 1fr;
|
|
||||||
gap:1em;
|
|
||||||
line-height: 1;
|
|
||||||
img,
|
|
||||||
audio,
|
|
||||||
video {
|
|
||||||
width: 150px;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.media-picker__buttons {
|
|
||||||
display: flex;
|
|
||||||
margin-top:1em;
|
|
||||||
gap: 0.5em;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
[theme="raw"] .media-picker__buttons {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-preview {
|
.media-preview-container[data-show="gallery"] {
|
||||||
border: 1px solid var(--border);
|
.media-picker {
|
||||||
border-radius: var(--radius-sm);
|
display: grid;
|
||||||
padding: var(--padding-form);
|
}
|
||||||
overflow-y: scroll;
|
.page-preview {
|
||||||
max-height: 100vh;
|
display: none;
|
||||||
svg {
|
}
|
||||||
max-width: 100%;
|
}
|
||||||
|
|
||||||
|
.media-preview-container[data-show="preview"] {
|
||||||
|
.media-picker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.page-preview {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-footer {
|
.edit-footer {
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--background);
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
padding: calc(20px / 2) 40px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: calc(20px / 4) calc(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 960px) {
|
@media (max-width: 960px) {
|
||||||
|
|
@ -417,16 +443,5 @@ max-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
form > * {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
form > * + *, .grouped > *+*{
|
|
||||||
margin-top: var(--margin-s);
|
|
||||||
margin-top: 10px;
|
|
||||||
width:100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,6 @@
|
||||||
// Focus-Aware Keystroke Pattern Detector with Redirects
|
// Focus-Aware Keystroke Pattern Detector with Redirects
|
||||||
// This script detects keyboard shortcuts only when NOT focusing on input elements
|
// This script detects keyboard shortcuts only when NOT focusing on input elements
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
// Variables to store keystroke history
|
// Variables to store keystroke history
|
||||||
let keystrokeHistory = '';
|
let keystrokeHistory = '';
|
||||||
|
|
@ -69,6 +70,117 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Autosize
|
||||||
|
!function (e, t) {
|
||||||
|
"object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e || self).autosize = t()
|
||||||
|
}(this, function () {
|
||||||
|
var e = new Map;
|
||||||
|
|
||||||
|
function t(t) {
|
||||||
|
var o = e.get(t);
|
||||||
|
o && o.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
function o(t) {
|
||||||
|
var o = e.get(t);
|
||||||
|
o && o.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = null;
|
||||||
|
return "undefined" == typeof window ? ((r = function (e) {
|
||||||
|
return e
|
||||||
|
}).destroy = function (e) {
|
||||||
|
return e
|
||||||
|
}, r.update = function (e) {
|
||||||
|
return e
|
||||||
|
}) : ((r = function (t, o) {
|
||||||
|
return t && Array.prototype.forEach.call(t.length ? t : [t], function (t) {
|
||||||
|
return function (t) {
|
||||||
|
if (t && t.nodeName && "TEXTAREA" === t.nodeName && !e.has(t)) {
|
||||||
|
var o, r = null, n = window.getComputedStyle(t), i = (o = t.value, function () {
|
||||||
|
s({
|
||||||
|
testForHeightReduction: "" === o || !t.value.startsWith(o),
|
||||||
|
restoreTextAlign: null
|
||||||
|
}), o = t.value
|
||||||
|
}), l = function (o) {
|
||||||
|
t.removeEventListener("autosize:destroy", l), t.removeEventListener("autosize:update", a), t.removeEventListener("input", i), window.removeEventListener("resize", a), Object.keys(o).forEach(function (e) {
|
||||||
|
return t.style[e] = o[e]
|
||||||
|
}), e.delete(t)
|
||||||
|
}.bind(t, {
|
||||||
|
height: t.style.height,
|
||||||
|
resize: t.style.resize,
|
||||||
|
textAlign: t.style.textAlign,
|
||||||
|
overflowY: t.style.overflowY,
|
||||||
|
overflowX: t.style.overflowX,
|
||||||
|
wordWrap: t.style.wordWrap
|
||||||
|
});
|
||||||
|
t.addEventListener("autosize:destroy", l), t.addEventListener("autosize:update", a), t.addEventListener("input", i), window.addEventListener("resize", a), t.style.overflowX = "hidden", t.style.wordWrap = "break-word", e.set(t, {
|
||||||
|
destroy: l,
|
||||||
|
update: a
|
||||||
|
}), a()
|
||||||
|
}
|
||||||
|
|
||||||
|
function s(e) {
|
||||||
|
var o, i, l = e.restoreTextAlign, a = void 0 === l ? null : l, d = e.testForHeightReduction,
|
||||||
|
u = void 0 === d || d, f = n.overflowY;
|
||||||
|
if (0 !== t.scrollHeight && ("vertical" === n.resize ? t.style.resize = "none" : "both" === n.resize && (t.style.resize = "horizontal"), u && (o = function (e) {
|
||||||
|
for (var t = []; e && e.parentNode && e.parentNode instanceof Element;) e.parentNode.scrollTop && t.push([e.parentNode, e.parentNode.scrollTop]), e = e.parentNode;
|
||||||
|
return function () {
|
||||||
|
return t.forEach(function (e) {
|
||||||
|
var t = e[0], o = e[1];
|
||||||
|
t.style.scrollBehavior = "auto", t.scrollTop = o, t.style.scrollBehavior = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}(t), t.style.height = ""), i = "content-box" === n.boxSizing ? t.scrollHeight - (parseFloat(n.paddingTop) + parseFloat(n.paddingBottom)) : t.scrollHeight + parseFloat(n.borderTopWidth) + parseFloat(n.borderBottomWidth), "none" !== n.maxHeight && i > parseFloat(n.maxHeight) ? ("hidden" === n.overflowY && (t.style.overflow = "scroll"), i = parseFloat(n.maxHeight)) : "hidden" !== n.overflowY && (t.style.overflow = "hidden"), t.style.height = i + "px", a && (t.style.textAlign = a), o && o(), r !== i && (t.dispatchEvent(new Event("autosize:resized", { bubbles: !0 })), r = i), f !== n.overflow && !a)) {
|
||||||
|
var c = n.textAlign;
|
||||||
|
"hidden" === n.overflow && (t.style.textAlign = "start" === c ? "end" : "start"), s({
|
||||||
|
restoreTextAlign: c,
|
||||||
|
testForHeightReduction: !0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function a() {
|
||||||
|
s({ testForHeightReduction: !0, restoreTextAlign: null })
|
||||||
|
}
|
||||||
|
}(t)
|
||||||
|
}), t
|
||||||
|
}).destroy = function (e) {
|
||||||
|
return e && Array.prototype.forEach.call(e.length ? e : [e], t), e
|
||||||
|
}, r.update = function (e) {
|
||||||
|
return e && Array.prototype.forEach.call(e.length ? e : [e], o), e
|
||||||
|
}), r
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleGallery(e) {
|
||||||
|
const mediasBtn = document.querySelector("#mediasBtn")
|
||||||
|
const previewBtn = document.querySelector("#previewBtn")
|
||||||
|
const mediaPreviewContainer = document.querySelector(".media-preview-container")
|
||||||
|
if (mediaPreviewContainer.dataset.show !== "gallery") {
|
||||||
|
mediaPreviewContainer.dataset.show = "gallery"
|
||||||
|
delete (mediasBtn.dataset.secondary)
|
||||||
|
previewBtn.setAttribute("data-secondary", "");
|
||||||
|
} else {
|
||||||
|
mediaPreviewContainer.dataset.show = false
|
||||||
|
mediasBtn.setAttribute("data-secondary", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePreview(e) {
|
||||||
|
const previewBtn = document.querySelector("#previewBtn")
|
||||||
|
const mediasBtn = document.querySelector("#mediasBtn")
|
||||||
|
const mediaPreviewContainer = document.querySelector(".media-preview-container")
|
||||||
|
if (mediaPreviewContainer.dataset.show !== "preview") {
|
||||||
|
mediaPreviewContainer.dataset.show = "preview"
|
||||||
|
delete (previewBtn.dataset.secondary)
|
||||||
|
mediasBtn.setAttribute("data-secondary", "");
|
||||||
|
} else {
|
||||||
|
mediaPreviewContainer.dataset.show = false
|
||||||
|
previewBtn.setAttribute("data-secondary", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let debounceTimer;
|
let debounceTimer;
|
||||||
function triggerDemo() {
|
function triggerDemo() {
|
||||||
clearTimeout(debounceTimer);
|
clearTimeout(debounceTimer);
|
||||||
|
|
@ -83,8 +195,8 @@ function triggerDemo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert text at saved cursor position (works even when not focused)
|
// Insert text at saved cursor position (works even when not focused)
|
||||||
function insertMedia(slug, extension, reposition, event) {
|
function insertMedia(slug, extension, reposition) {
|
||||||
event.preventDefault()
|
console.log({ reposition })
|
||||||
const value = textarea.value;
|
const value = textarea.value;
|
||||||
let textInsert = ""
|
let textInsert = ""
|
||||||
|
|
||||||
|
|
@ -110,15 +222,15 @@ function insertMedia(slug, extension, reposition, event) {
|
||||||
textarea.setSelectionRange(savedCursor, savedCursor);
|
textarea.setSelectionRange(savedCursor, savedCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// togglePreview()
|
togglePreview()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
triggerDemo()
|
triggerDemo()
|
||||||
}, 400)
|
}, 100)
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteMedia(filename, event) {
|
function deleteMedia(filename) {
|
||||||
event.preventDefault()
|
|
||||||
if (!confirm(`Delete "${filename}"?`)) return;
|
if (!confirm(`Delete "${filename}"?`)) return;
|
||||||
|
|
||||||
const csrfToken = document.querySelector('#csrf_token')?.value;
|
const csrfToken = document.querySelector('#csrf_token')?.value;
|
||||||
|
|
||||||
fetch(`/files/delete/${encodeURIComponent(filename)}`, {
|
fetch(`/files/delete/${encodeURIComponent(filename)}`, {
|
||||||
|
|
@ -130,7 +242,7 @@ function deleteMedia(filename, event) {
|
||||||
const responseText = await response.text();
|
const responseText = await response.text();
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const mediaItem = document.querySelector(`#${filename.split('.').slice(0, -1)[0]}`)
|
const mediaItem = document.querySelector(`#${filename.split('.').slice(0, -1)}`)
|
||||||
if (mediaItem) {
|
if (mediaItem) {
|
||||||
mediaItem.remove()
|
mediaItem.remove()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue