diff --git a/admin/api/designer.go b/admin/api/designer.go index a31349d906a872a6f48e36620083ca68582bd9c0..0f22aceb8dd23be9ba6340b6b62c8e88b7e71a03 100644 --- a/admin/api/designer.go +++ b/admin/api/designer.go @@ -39,13 +39,12 @@ func createFontDesigner(w http.ResponseWriter, r *http.Request) { return } + w.WriteHeader(http.StatusCreated) err = json.NewEncoder(w).Encode(newDesigner) if err != nil { http.Error(w, "Failed to encode body", http.StatusInternalServerError) return } - - w.WriteHeader(http.StatusCreated) } func deleteFontDesigner(w http.ResponseWriter, r *http.Request) { diff --git a/static/admin/css/cosmo-edits.css b/static/admin/css/cosmo-edits.css index 974e15dccb06b99c54c6cdbdc9957679e1cc3bba..7309df9392ac3d63e023282e88d91b5d477e2db6 100644 --- a/static/admin/css/cosmo-edits.css +++ b/static/admin/css/cosmo-edits.css @@ -13,4 +13,8 @@ .cosmo-tab__content.is--designer { height: calc(var(--page-height) - var(--tab-links-height) - var(--tab-gap) - var(--title-font-size) - 0.5rem); -} \ No newline at end of file +} + +.cosmo-modal { + max-width: max(30vw, 30rem); +} diff --git a/static/admin/css/fonts.css b/static/admin/css/fonts.css index 40624a57e0b977cf04c4c495bce6f1592ceed1bb..683d5c5cc3c6e24455fb391b4bdfe46568b3086c 100644 --- a/static/admin/css/fonts.css +++ b/static/admin/css/fonts.css @@ -11,3 +11,7 @@ .is--loading { overflow: hidden; } + +.font-description { + width: 40rem; +} diff --git a/static/admin/css/form.css b/static/admin/css/form.css index a30ad389797e9bcd46285117379d9dca9d1e158b..8e0b897a2b908729143847bcab07d151ff61a939 100644 --- a/static/admin/css/form.css +++ b/static/admin/css/form.css @@ -1,26 +1,26 @@ .jinya-fieldset { - min-width: 0; - padding: 0; - margin: 0; - border: 0; - grid-column: span 2; + min-width: 0; + padding: 0; + margin: 0; + border: 0; + grid-column: span 2; } .jinya-legend { - font-size: var(--input-header-font-size); - height: var(--input-header-font-size); - font-weight: var(--font-weight-light); - font-family: var(--font-family-heading); - text-transform: uppercase; - grid-column: span 2; - margin-top: var(--input-group-gap); - margin-bottom: var(--input-group-gap); + font-size: var(--input-header-font-size); + height: var(--input-header-font-size); + font-weight: var(--font-weight-light); + font-family: var(--font-family-heading); + text-transform: uppercase; + grid-column: span 2; + margin-top: var(--input-group-gap); + margin-bottom: var(--input-group-gap); } jinya-toolbar-editor { - grid-column: span 2; + grid-column: span 2; } .top-gap { - margin-top: 1rem; + margin-top: 1rem; } diff --git a/static/admin/js/font/detail.js b/static/admin/js/font/detail.js index 778c98d66aa99dee4373b0534727ace94fd106dc..d5448c9bababc6cb59c8456d8a306eee7d1d6814 100644 --- a/static/admin/js/font/detail.js +++ b/static/admin/js/font/detail.js @@ -1,5 +1,5 @@ import { Alpine } from '../../../lib/alpine.js'; -import { get, httpDelete, post } from '../../../lib/jinya-http.js'; +import { get, httpDelete, post, put } from '../../../lib/jinya-http.js'; import '../../lib/ui/toolbar-editor.js'; import confirm from '../../lib/ui/confirm.js'; @@ -11,10 +11,23 @@ Alpine.data('fontDetailsData', () => ({ font: null, activeDesigner: null, editDesignerOn: false, + editFontOpen: false, newDesigner: { name: '', bio: '', }, + newFileOpen: false, + newFile: { + file: undefined, + weight: '400', + style: 'normal', + }, + editFileOpen: false, + editFile: { + file: undefined, + weight: '400', + style: 'normal', + }, get fontName() { return this.$router.params.name; }, @@ -29,11 +42,42 @@ Alpine.data('fontDetailsData', () => ({ this.activeSideItem = item; }, openEditFontDialog() { + this.font.editDescription = this.font.description; + this.font.editLicense = this.font.license; + this.font.editCategory = this.font.category; + this.editFontOpen = true; + }, + openNewFileDialog() { + this.newFile = { + file: undefined, + weight: '400', + style: 'normal', + }; + this.newFileOpen = true; + }, + openEditFileDialog(file) { + this.editFile = { + file: undefined, + weight: file.weight, + style: file.style, + }; + this.editFileOpen = true; }, editDesigner() { this.activeDesigner.editBio = this.activeDesigner.bio; this.editDesignerOn = true; }, + async updateFont() { + this.font.description = this.font.editDescription; + this.font.license = this.font.editLicense; + this.font.category = this.font.editCategory; + await put(`/api/admin/font/${this.fontName}`, { + description: this.font.editDescription, + license: this.font.editLicense, + category: this.font.editCategory, + }); + this.editFontOpen = false; + }, async createNewDesigner() { const newDesigner = await post(`/api/admin/font/${this.fontName}/designer`, this.newDesigner); this.font.designers = [...this.font.designers, newDesigner]; @@ -46,7 +90,7 @@ Alpine.data('fontDetailsData', () => ({ bio: this.activeDesigner.editBio, name: this.activeDesigner.name, }); - this.font.designers = [...this.font.designers.filter(d => d.name !== this.activeDesigner.name), updatedDesigner]; + this.font.designers = [...this.font.designers.filter((d) => d.name !== this.activeDesigner.name), updatedDesigner]; this.activeDesigner = updatedDesigner; this.activeDesigner.editBio = updatedDesigner.bio; this.editDesignerOn = false; @@ -76,8 +120,41 @@ Alpine.data('fontDetailsData', () => ({ }) ) { await httpDelete(`/api/admin/font/${this.fontName}/designer/${this.activeDesigner.name}`); - this.font.designers = this.font.designers.filter(d => d.name !== this.activeDesigner.name); + this.font.designers = this.font.designers.filter((d) => d.name !== this.activeDesigner.name); this.activeDesigner = this.font.designers.at(0) ?? null; } }, + async deleteFile(file) { + if ( + await confirm({ + title: localize({ key: 'delete-file-title' }), + approveLabel: localize({ key: 'delete-file-confirm-label' }), + declineLabel: localize({ key: 'delete-file-decline-label' }), + message: localize({ + key: 'delete-file-message', + values: { + type: file.type, + weight: localize({ key: `font-weight-${file.weight}` }), + style: localize({ key: `font-style-${file.style}` }), + }, + }), + negative: true, + }) + ) { + await httpDelete(`/api/admin/font/${this.fontName}/file/${file.weight}.${file.style}.${file.type}`); + this.font.fonts = this.font.fonts.filter((f) => f.path !== file.path); + } + }, + async createNewFile() { + const type = this.newFile.file.name.split('.').reverse()[0]; + await post(`/api/admin/font/${this.fontName}/file/${this.newFile.weight}.${this.newFile.style}.${type}`, this.newFile.file); + this.font.fonts = await get(`/api/admin/font/${this.fontName}/file`); + this.newFileOpen = false; + }, + async updateFile() { + const type = this.editFile.file.name.split('.').reverse()[0]; + await post(`/api/admin/font/${this.fontName}/file/${this.editFile.weight}.${this.editFile.style}.${type}`, this.editFile.file); + this.font.fonts = await get(`/api/admin/font/${this.fontName}/file`); + this.editFileOpen = false; + }, })); diff --git a/static/admin/langs/messages.de.json b/static/admin/langs/messages.de.json index dcdee3f28b28109f8e1a2b4e06868beda24262ca..b9c7acd1a9d2b28d5fe0033c2914e02a875d35ec 100644 --- a/static/admin/langs/messages.de.json +++ b/static/admin/langs/messages.de.json @@ -76,7 +76,7 @@ "delete-file-title": "Datei entfernen", "delete-file-confirm-label": "Datei entfernen", "delete-file-decline-label": "Datei nicht entfernen", - "delete-file-message": "Soll die {name}-Datei mit der Stärke {style} im Stil {style}", + "delete-file-message": "Soll die {type}-Datei mit der Stärke {weight} im Stil {style} wirklich gelöscht werden?", "font-list-all-fonts": "Alle Schriften", "font-list-google-fonts": "Schriften von Google", "font-list-custom-fonts": "Eigene Schriften ", diff --git a/static/admin/langs/messages.en.json b/static/admin/langs/messages.en.json index 3d6f10e2d67753fdabcf713a74e48bf5bf66966e..367bc7fa8acc5f716c800b54c5b6eb8a8caa8cd2 100644 --- a/static/admin/langs/messages.en.json +++ b/static/admin/langs/messages.en.json @@ -76,7 +76,7 @@ "delete-file-title": "Remove file", "delete-file-confirm-label": "Remove file", "delete-file-decline-label": "Keep file", - "delete-file-message": "Do you really want to remove the {name}-file with a weight of {weight} in font style {style}", + "delete-file-message": "Do you really want to remove the {type}-file with a weight of {weight} in font style {style}", "font-list-all-fonts": "All fonts", "font-list-google-fonts": "Google Fonts", "font-list-custom-fonts": "Custom fonts", diff --git a/static/admin/langs/messages.json b/static/admin/langs/messages.json index 3d6f10e2d67753fdabcf713a74e48bf5bf66966e..367bc7fa8acc5f716c800b54c5b6eb8a8caa8cd2 100644 --- a/static/admin/langs/messages.json +++ b/static/admin/langs/messages.json @@ -76,7 +76,7 @@ "delete-file-title": "Remove file", "delete-file-confirm-label": "Remove file", "delete-file-decline-label": "Keep file", - "delete-file-message": "Do you really want to remove the {name}-file with a weight of {weight} in font style {style}", + "delete-file-message": "Do you really want to remove the {type}-file with a weight of {weight} in font style {style}", "font-list-all-fonts": "All fonts", "font-list-google-fonts": "Google Fonts", "font-list-custom-fonts": "Custom fonts", diff --git a/static/admin/lib/ui/jodit.js b/static/admin/lib/ui/jodit.js index d49ab3cdae1763e165eb7fd1dc181e18bfd96604..ed713dc5b69608e3ae8e41548e32db85b5dc226e 100755 --- a/static/admin/lib/ui/jodit.js +++ b/static/admin/lib/ui/jodit.js @@ -152,7 +152,7 @@ function getFullToolbar() { ]; } -export function createJodit(idOrElement, inline = false, height = undefined) { +export function createJodit(idOrElement, inline = false, allowFullscreen = false, height = undefined) { setJoditIcons(); const data = { toolbar: !inline, @@ -161,7 +161,7 @@ export function createJodit(idOrElement, inline = false, height = undefined) { showXPathInStatusbar: false, minHeight: '11rem', disablePlugins: - 'about,add-new-line,ai-assistant,class-span,clean-html,clipboard,copyformat,dtd,file,font,hr,iframe,image,image-properties,indent,key-arrow-outside,line-height,mobile,xpath,table-keyboard-navigation,tab,symbols,stat,spellcheck,speech-recognize,search,resize-cells,redo-undo,print,preview,powered-by-jodit,paste-storage,paste-from-word,video,wrap-nodes,limit', + 'about,add-new-line,ai-assistant,class-span,clean-html,clipboard,copyformat,dtd,file,font,hr,iframe,image,image-properties,indent,key-arrow-outside,line-height,mobile,xpath,table-keyboard-navigation,tab,symbols,stat,spellcheck,speech-recognize,search,resize-cells,redo-undo,print,preview,powered-by-jodit,paste-storage,paste-from-word,video,wrap-nodes,limit,fullscreen', inline, toolbarInline: true, toolbarInlineForSelection: true, @@ -177,7 +177,7 @@ export function createJodit(idOrElement, inline = false, height = undefined) { if (height) { data.height = height; } - if (!inline) { + if (allowFullscreen) { data.buttons = getFullToolbar(); data.extraButtons = ['fullsize', 'source']; } diff --git a/static/admin/lib/ui/toolbar-editor.js b/static/admin/lib/ui/toolbar-editor.js index 3e3f1294e70558473208ff2c4b81b74cb83dcd1f..dff68f97050ebe28f3d3077619f93647119b8de0 100755 --- a/static/admin/lib/ui/toolbar-editor.js +++ b/static/admin/lib/ui/toolbar-editor.js @@ -18,7 +18,7 @@ class ToolbarEditorElement extends HTMLElement { </style> <textarea></textarea> `; - this.editor = createJodit(this.root.querySelector('textarea'), false, this.height); + this.editor = createJodit(this.root.querySelector('textarea'), false, false, this.height); this.editor.value = this.content; this.editor.events.on('change', (e) => { this.dispatchEvent(new EditorChangeEvent(e)); diff --git a/static/admin/templates/font/detail.html b/static/admin/templates/font/detail.html index 1059052e32a178330ef45da7dad9a675838518fa..cafac3f8e3a4e050b0179b793868c15926a6f61a 100644 --- a/static/admin/templates/font/detail.html +++ b/static/admin/templates/font/detail.html @@ -47,7 +47,47 @@ <dd x-boolean-display="font.googleFont"></dd> </dl> <h4 x-localize:font-description=""></h4> - <div x-html="font.description"></div> + <div class="font-description" x-html="font.description"></div> + <template x-if="editFontOpen"> + <div class="cosmo-modal__container"> + <form class="cosmo-modal" @submit.prevent="updateFont"> + <h1 class="cosmo-modal__title" x-localize:edit-font-title="font"></h1> + <div class="cosmo-modal__content"> + <div class="cosmo-input__group"> + <label for="category" class="cosmo-label" x-localize:font-category=""></label> + <select class="cosmo-select" id="category" x-model="font.editCategory"> + <option value="Sans Serif" x-localize:font-style-sans-serif=""></option> + <option value="Serif" x-localize:font-style-serif=""></option> + <option value="Monospace" x-localize:font-style-monospace=""></option> + <option value="Display" x-localize:font-style-display=""></option> + <option value="Handwriting" x-localize:font-style-handwriting=""></option> + </select> + <label for="license" class="cosmo-label" x-localize:font-license=""></label> + <input required list="suggestedLicenses" type="text" id="license" class="cosmo-input" x-model="font.editLicense" /> + <template x-if="!font.editLicense"> + <span class="cosmo-input__message is--negative" x-localize:font-license-error=""></span> + </template> + <datalist id="suggestedLicenses"> + <option value="ufl"></option> + <option value="ofl"></option> + <option value="apache2"></option> + <option value="mit"></option> + <option value="cc0"></option> + </datalist> + <span class="cosmo-input__header" x-localize:font-description=""></span> + <jinya-toolbar-editor + :content="font.editDescription" + @change="(e) => font.editDescription = e.value" + ></jinya-toolbar-editor> + </div> + </div> + <div class="cosmo-modal__button-bar"> + <button class="cosmo-button" @click="editFontOpen = false" x-localize:discard-changes=""></button> + <button class="cosmo-button" type="submit" x-localize:save-font=""></button> + </div> + </form> + </div> + </template> </div> </template> <template x-if="activeSideItem === 'designers'"> @@ -81,18 +121,19 @@ <template x-if="!font.googleFont"> <div class="cosmo-toolbar"> <div class="cosmo-toolbar__group"> - <button class="cosmo-button" :disabled="editDesignerOn" @click="editDesigner" - x-localize:edit=""></button> + <button class="cosmo-button" :disabled="editDesignerOn" @click="editDesigner" x-localize:edit=""></button> <button class="cosmo-button" @click="removeDesigner" x-localize:delete=""></button> </div> </div> </template> <template x-if="editDesignerOn"> <form class="top-gap" @submit.prevent="updateDesigner()"> - <jinya-toolbar-editor :content="activeDesigner.editBio" @change="(e) => activeDesigner.editBio = e.value"></jinya-toolbar-editor> + <jinya-toolbar-editor + :content="activeDesigner.editBio" + @change="(e) => activeDesigner.editBio = e.value" + ></jinya-toolbar-editor> <div class="cosmo-button__container"> - <button class="cosmo-button" @click="editDesignerOn = false" type="reset" - x-localize:discard-changes=""></button> + <button class="cosmo-button" @click="editDesignerOn = false" type="reset" x-localize:discard-changes=""></button> <button class="cosmo-button is--primary" type="submit" x-localize:save-designer=""></button> </div> </form> @@ -131,40 +172,122 @@ <template x-if="!font.googleFont"> <div class="cosmo-toolbar"> <div class="cosmo-toolbar__group"> - <button class="cosmo-button" @click="addFileDialogOpen = true" x-localize:upload-file=""></button> + <button class="cosmo-button" @click="openNewFileDialog" x-localize:upload-file=""></button> </div> </div> </template> <table class="cosmo-table"> <thead> - <tr> - <th x-localize:font-style=""></th> - <th x-localize:font-weight=""></th> - <th x-localize:font-file-type=""></th> - <th x-localize:actions=""></th> - </tr> - </thead> - <tbody> - <template x-for="file in font.fonts"> <tr> - <td x-text="file.style"></td> - <td x-text="file.weight"></td> - <td x-text="file.type"></td> - <td> - <div class="cosmo-toolbar__group"> - <a :href="file.path" class="cosmo-button" x-localize:download=""></a> - <template x-if="!font.googleFont"> - <button class="cosmo-button" @click="openEditFile(file)" x-localize:edit=""></button> - </template> - <template x-if="!font.googleFont"> - <button class="cosmo-button" @click="openDeleteFile(file)" x-localize:delete=""></button> - </template> - </div> - </td> + <th x-localize:font-style=""></th> + <th x-localize:font-weight=""></th> + <th x-localize:font-file-type=""></th> + <th x-localize:actions=""></th> </tr> - </template> + </thead> + <tbody> + <template x-for="file in font.fonts"> + <tr> + <td x-text="file.style"></td> + <td x-text="file.weight"></td> + <td x-text="file.type"></td> + <td> + <div class="cosmo-toolbar__group"> + <a :href="file.path" native class="cosmo-button" x-localize:download=""></a> + <template x-if="!font.googleFont"> + <button class="cosmo-button" @click="openEditFileDialog(file)" x-localize:edit=""></button> + </template> + <template x-if="!font.googleFont"> + <button class="cosmo-button" @click="deleteFile(file)" x-localize:delete=""></button> + </template> + </div> + </td> + </tr> + </template> </tbody> </table> + <template x-if="newFileOpen"> + <div class="cosmo-modal__container"> + <form class="cosmo-modal" @submit.prevent="createNewFile"> + <h1 class="cosmo-modal__title" x-localize:create-file-title=""></h1> + <div class="cosmo-modal__content"> + <div class="cosmo-input__group"> + <label for="weight" class="cosmo-label" x-localize:font-weight-label=""></label> + <select class="cosmo-select" id="weight" x-model="newFile.weight"> + <option value="100" x-localize:font-weight-100=""></option> + <option value="200" x-localize:font-weight-200=""></option> + <option value="300" x-localize:font-weight-300=""></option> + <option value="400" x-localize:font-weight-400=""></option> + <option value="500" x-localize:font-weight-500=""></option> + <option value="600" x-localize:font-weight-600=""></option> + <option value="700" x-localize:font-weight-700=""></option> + <option value="800" x-localize:font-weight-800=""></option> + <option value="900" x-localize:font-weight-900=""></option> + </select> + <label for="style" class="cosmo-label" x-localize:font-style-label=""></label> + <select class="cosmo-select" id="style" x-model="newFile.style"> + <option value="normal" x-localize:font-style-normal=""></option> + <option value="italic" x-localize:font-style-italic=""></option> + </select> + <label for="file" class="cosmo-label" x-localize:font-file=""></label> + <input + required + type="file" + id="file" + class="cosmo-input" + accept="font/woff2, font/ttf" + @change="(e) => newFile.file = e.target.files.item(0)" + /> + </div> + </div> + <div class="cosmo-modal__button-bar"> + <button class="cosmo-button" type="button" @click="newFileOpen = false" x-localize:cancel=""></button> + <button class="cosmo-button" type="submit" x-localize:upload-file=""></button> + </div> + </form> + </div> + </template> + <template x-if="editFileOpen"> + <div class="cosmo-modal__container"> + <form class="cosmo-modal" @submit.prevent="updateFile"> + <h1 class="cosmo-modal__title" x-localize:upload-file=""></h1> + <div class="cosmo-modal__content"> + <div class="cosmo-input__group"> + <label for="style" class="cosmo-label" x-localize:font-style-label=""></label> + <select disabled class="cosmo-select" id="style" x-model="editFile.style"> + <option value="normal" x-localize:font-style-normal=""></option> + <option value="italic" x-localize:font-style-italic=""></option> + </select> + <label for="weight" class="cosmo-label" x-localize:font-weight-label=""></label> + <select disabled class="cosmo-select" id="weight" x-model="editFile.weight"> + <option value="100" x-localize:font-weight-100=""></option> + <option value="200" x-localize:font-weight-200=""></option> + <option value="300" x-localize:font-weight-300=""></option> + <option value="400" x-localize:font-weight-400=""></option> + <option value="500" x-localize:font-weight-500=""></option> + <option value="600" x-localize:font-weight-600=""></option> + <option value="700" x-localize:font-weight-700=""></option> + <option value="800" x-localize:font-weight-800=""></option> + <option value="900" x-localize:font-weight-900=""></option> + </select> + <label for="file" class="cosmo-label" x-localize:font-file=""></label> + <input + required + type="file" + id="file" + class="cosmo-input" + accept="font/woff2, font/ttf" + @change="(e) => editFile.file = e.target.files.item(0)" + /> + </div> + </div> + <div class="cosmo-modal__button-bar"> + <button class="cosmo-button" type="button" @click="editFileOpen = false" x-localize:cancel=""></button> + <button class="cosmo-button" type="submit" x-localize:upload-file=""></button> + </div> + </form> + </div> + </template> </div> </template> </div> diff --git a/static/admin/templates/font/page.html b/static/admin/templates/font/page.html index 02c982f73459c82767b874a1013e2008ae84ac83..3cbd0ab7ce6f8d65f59d9d5a513cf5de036f1d89 100644 --- a/static/admin/templates/font/page.html +++ b/static/admin/templates/font/page.html @@ -97,11 +97,9 @@ <div class="cosmo-input__group"> <label for="family" class="cosmo-label" x-localize:font-family=""></label> <input required type="text" id="family" class="cosmo-input" x-model="newFont.family" /> - <span - class="cosmo-input__message is--negative" - :class="{ 'is--hidden': !newFont.family }" - x-localize:font-family-error="" - ></span> + <template x-if="!newFont.family"> + <span class="cosmo-input__message is--negative" x-localize:font-family-error=""></span> + </template> <label for="category" class="cosmo-label" x-localize:font-category=""></label> <select class="cosmo-select" id="category" x-model="newFont.category"> <option value="Sans Serif" x-localize:font-style-sans-serif=""></option> @@ -112,11 +110,9 @@ </select> <label for="license" class="cosmo-label" x-localize:font-license=""></label> <input required list="suggestedLicenses" type="text" id="license" class="cosmo-input" x-model="newFont.license" /> - <span - class="cosmo-input__message is--negative" - :class="{ 'is--hidden': !newFont.license }" - x-localize:font-license-error="" - ></span> + <template x-if="!newFont.license"> + <span class="cosmo-input__message is--negative" x-localize:font-license-error=""></span> + </template> <datalist id="suggestedLicenses"> <option value="ufl"></option> <option value="ofl"></option> @@ -125,7 +121,7 @@ <option value="cc0"></option> </datalist> <span class="cosmo-input__header" x-localize:font-description=""></span> - <jinya-toolbar-editor x-model="newFont.description"></jinya-toolbar-editor> + <jinya-toolbar-editor :content="newFont.description" @change="(e) => newFont.description = e.value"></jinya-toolbar-editor> </div> </div> <div class="cosmo-modal__button-bar"> diff --git a/static/lib/jinya-http.js b/static/lib/jinya-http.js index f9339b7f3261ce030e031234b2d054a60588f31d..cdb3ad08e391ae74af44fd2aabf7a75e4e0f3635 100644 --- a/static/lib/jinya-http.js +++ b/static/lib/jinya-http.js @@ -67,7 +67,7 @@ export async function send( const response = await fetch(url, request); if (response.ok) { - if (response.status !== 204) { + if (response.status !== 204 && response.headers.get('content-length') > 0) { if (plain) { return await response.text(); }