✨ Better categories
This commit is contained in:
		
							
								
								
									
										89
									
								
								pkg/server/categories_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								pkg/server/categories_api.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"code.smartsheep.studio/hydrogen/interactive/pkg/models"
 | 
			
		||||
	"code.smartsheep.studio/hydrogen/interactive/pkg/services"
 | 
			
		||||
	"github.com/gofiber/fiber/v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func listCategroies(c *fiber.Ctx) error {
 | 
			
		||||
	categories, err := services.ListCategory()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return c.JSON(categories)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newCategory(c *fiber.Ctx) error {
 | 
			
		||||
	user := c.Locals("principal").(models.Account)
 | 
			
		||||
	if user.PowerLevel <= 55 {
 | 
			
		||||
		return fiber.NewError(fiber.StatusForbidden, "require power level 55 to create categories")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Alias       string `json:"alias" validate:"required"`
 | 
			
		||||
		Name        string `json:"name" validate:"required"`
 | 
			
		||||
		Description string `json:"description"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := BindAndValidate(c, &data); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	category, err := services.NewCategory(data.Alias, data.Name, data.Description)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return c.JSON(category)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func editCategory(c *fiber.Ctx) error {
 | 
			
		||||
	user := c.Locals("principal").(models.Account)
 | 
			
		||||
	if user.PowerLevel <= 55 {
 | 
			
		||||
		return fiber.NewError(fiber.StatusForbidden, "require power level 55 to edit categories")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	id, _ := c.ParamsInt("categoryId", 0)
 | 
			
		||||
	category, err := services.GetCategoryWithID(uint(id))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Alias       string `json:"alias" validate:"required"`
 | 
			
		||||
		Name        string `json:"name" validate:"required"`
 | 
			
		||||
		Description string `json:"description"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := BindAndValidate(c, &data); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	category, err = services.EditCategory(category, data.Alias, data.Name, data.Description)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return c.JSON(category)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func deleteCategory(c *fiber.Ctx) error {
 | 
			
		||||
	user := c.Locals("principal").(models.Account)
 | 
			
		||||
	if user.PowerLevel <= 55 {
 | 
			
		||||
		return fiber.NewError(fiber.StatusForbidden, "require power level 55 to delete categories")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	id, _ := c.ParamsInt("categoryId", 0)
 | 
			
		||||
	category, err := services.GetCategoryWithID(uint(id))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fiber.NewError(fiber.StatusNotFound, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := services.DeleteCategory(category); err != nil {
 | 
			
		||||
		return fiber.NewError(fiber.StatusBadRequest, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return c.JSON(category)
 | 
			
		||||
}
 | 
			
		||||
@@ -54,7 +54,7 @@ func listAvailableRealm(c *fiber.Ctx) error {
 | 
			
		||||
func createRealm(c *fiber.Ctx) error {
 | 
			
		||||
	user := c.Locals("principal").(models.Account)
 | 
			
		||||
	if user.PowerLevel < 10 {
 | 
			
		||||
		return fiber.NewError(fiber.StatusForbidden, "require power level 10 to create realm")
 | 
			
		||||
		return fiber.NewError(fiber.StatusForbidden, "require power level 10 to create realms")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var data struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,10 @@
 | 
			
		||||
package server
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.smartsheep.studio/hydrogen/interactive/pkg/view"
 | 
			
		||||
	"github.com/gofiber/fiber/v2"
 | 
			
		||||
	"github.com/gofiber/fiber/v2/middleware/cache"
 | 
			
		||||
@@ -11,9 +15,6 @@ import (
 | 
			
		||||
	jsoniter "github.com/json-iterator/go"
 | 
			
		||||
	"github.com/rs/zerolog/log"
 | 
			
		||||
	"github.com/spf13/viper"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var A *fiber.App
 | 
			
		||||
@@ -76,6 +77,11 @@ func NewServer() {
 | 
			
		||||
		api.Put("/posts/:postId", auth, editPost)
 | 
			
		||||
		api.Delete("/posts/:postId", auth, deletePost)
 | 
			
		||||
 | 
			
		||||
		api.Get("/categories", listCategroies)
 | 
			
		||||
		api.Post("/categories", auth, newCategory)
 | 
			
		||||
		api.Put("/categories/:categoryId", auth, editCategory)
 | 
			
		||||
		api.Delete("/categories/:categoryId", auth, deleteCategory)
 | 
			
		||||
 | 
			
		||||
		api.Get("/creators/posts", auth, listOwnPost)
 | 
			
		||||
		api.Get("/creators/posts/:postId", auth, getOwnPost)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,66 @@
 | 
			
		||||
package services
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
 | 
			
		||||
	"code.smartsheep.studio/hydrogen/interactive/pkg/database"
 | 
			
		||||
	"code.smartsheep.studio/hydrogen/interactive/pkg/models"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func GetCategory(alias, name string) (models.Category, error) {
 | 
			
		||||
func ListCategory() ([]models.Category, error) {
 | 
			
		||||
	var categories []models.Category
 | 
			
		||||
 | 
			
		||||
	err := database.C.Find(&categories).Error
 | 
			
		||||
 | 
			
		||||
	return categories, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetCategory(alias string) (models.Category, error) {
 | 
			
		||||
	var category models.Category
 | 
			
		||||
	if err := database.C.Where(models.Category{Alias: alias}).First(&category).Error; err != nil {
 | 
			
		||||
		if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
			
		||||
			category = models.Category{
 | 
			
		||||
				Alias: alias,
 | 
			
		||||
				Name:  name,
 | 
			
		||||
			}
 | 
			
		||||
			err := database.C.Save(&category).Error
 | 
			
		||||
			return category, err
 | 
			
		||||
		}
 | 
			
		||||
		return category, err
 | 
			
		||||
	}
 | 
			
		||||
	return category, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetTag(alias, name string) (models.Tag, error) {
 | 
			
		||||
func GetCategoryWithID(id uint) (models.Category, error) {
 | 
			
		||||
	var category models.Category
 | 
			
		||||
	if err := database.C.Where(models.Category{
 | 
			
		||||
		BaseModel: models.BaseModel{ID: id},
 | 
			
		||||
	}).First(&category).Error; err != nil {
 | 
			
		||||
		return category, err
 | 
			
		||||
	}
 | 
			
		||||
	return category, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCategory(alias, name, description string) (models.Category, error) {
 | 
			
		||||
	category := models.Category{
 | 
			
		||||
		Alias:       alias,
 | 
			
		||||
		Name:        name,
 | 
			
		||||
		Description: description,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := database.C.Save(&category).Error
 | 
			
		||||
 | 
			
		||||
	return category, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EditCategory(category models.Category, alias, name, description string) (models.Category, error) {
 | 
			
		||||
	category.Alias = alias
 | 
			
		||||
	category.Name = name
 | 
			
		||||
	category.Description = description
 | 
			
		||||
 | 
			
		||||
	err := database.C.Save(&category).Error
 | 
			
		||||
 | 
			
		||||
	return category, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DeleteCategory(category models.Category) error {
 | 
			
		||||
	return database.C.Delete(category).Error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetTagOrCreate(alias, name string) (models.Tag, error) {
 | 
			
		||||
	var tag models.Tag
 | 
			
		||||
	if err := database.C.Where(models.Category{Alias: alias}).First(&tag).Error; err != nil {
 | 
			
		||||
		if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,9 +3,10 @@ package services
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gofiber/fiber/v2"
 | 
			
		||||
	"github.com/rs/zerolog/log"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.smartsheep.studio/hydrogen/interactive/pkg/database"
 | 
			
		||||
	"code.smartsheep.studio/hydrogen/interactive/pkg/models"
 | 
			
		||||
@@ -141,13 +142,13 @@ func NewPost(
 | 
			
		||||
	var err error
 | 
			
		||||
	var post models.Post
 | 
			
		||||
	for idx, category := range categories {
 | 
			
		||||
		categories[idx], err = GetCategory(category.Alias, category.Name)
 | 
			
		||||
		categories[idx], err = GetCategory(category.Alias)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return post, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for idx, tag := range tags {
 | 
			
		||||
		tags[idx], err = GetTag(tag.Alias, tag.Name)
 | 
			
		||||
		tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return post, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -248,13 +249,13 @@ func EditPost(
 | 
			
		||||
) (models.Post, error) {
 | 
			
		||||
	var err error
 | 
			
		||||
	for idx, category := range categories {
 | 
			
		||||
		categories[idx], err = GetCategory(category.Alias, category.Name)
 | 
			
		||||
		categories[idx], err = GetCategory(category.Alias)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return post, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for idx, tag := range tags {
 | 
			
		||||
		tags[idx], err = GetTag(tag.Alias, tag.Name)
 | 
			
		||||
		tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return post, err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,24 +5,34 @@ import { getAtk, useUserinfo } from "../../stores/userinfo.tsx";
 | 
			
		||||
import styles from "./PostPublish.module.css";
 | 
			
		||||
 | 
			
		||||
export default function PostEditActions(props: {
 | 
			
		||||
  editing?: any,
 | 
			
		||||
  onInputAlias: (value: string) => void,
 | 
			
		||||
  onInputPublish: (value: string) => void,
 | 
			
		||||
  onInputAttachments: (value: any[]) => void,
 | 
			
		||||
  onInputCategories: (categories: any[]) => void,
 | 
			
		||||
  onInputTags: (tags: any[]) => void,
 | 
			
		||||
  onError: (message: string | null) => void,
 | 
			
		||||
  editing?: any;
 | 
			
		||||
  onInputAlias: (value: string) => void;
 | 
			
		||||
  onInputPublish: (value: string) => void;
 | 
			
		||||
  onInputAttachments: (value: any[]) => void;
 | 
			
		||||
  onInputCategories: (categories: any[]) => void;
 | 
			
		||||
  onInputTags: (tags: any[]) => void;
 | 
			
		||||
  onError: (message: string | null) => void;
 | 
			
		||||
}) {
 | 
			
		||||
  const userinfo = useUserinfo();
 | 
			
		||||
 | 
			
		||||
  const [uploading, setUploading] = createSignal(false);
 | 
			
		||||
 | 
			
		||||
  const [attachments, setAttachments] = createSignal<any[]>(props.editing?.attachments ?? []);
 | 
			
		||||
  const [categories, setCategories] = createSignal<{ alias: string, name: string }[]>(props.editing?.categories ?? []);
 | 
			
		||||
  const [tags, setTags] = createSignal<{ alias: string, name: string }[]>(props.editing?.tags ?? []);
 | 
			
		||||
  const [categories, setCategories] = createSignal<{ alias: string; name: string }[]>(props.editing?.categories ?? []);
 | 
			
		||||
  const [tags, setTags] = createSignal<{ alias: string; name: string }[]>(props.editing?.tags ?? []);
 | 
			
		||||
 | 
			
		||||
  const [availableCategories, setAvailableCategories] = createSignal<any[]>([]);
 | 
			
		||||
  const [attachmentMode, setAttachmentMode] = createSignal(0);
 | 
			
		||||
 | 
			
		||||
  async function readCategories() {
 | 
			
		||||
    const res = await fetch("/api/categories");
 | 
			
		||||
    if (res.status === 200) {
 | 
			
		||||
      setAvailableCategories(await res.json());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  readCategories();
 | 
			
		||||
 | 
			
		||||
  async function uploadAttachment(evt: SubmitEvent) {
 | 
			
		||||
    evt.preventDefault();
 | 
			
		||||
 | 
			
		||||
@@ -33,8 +43,8 @@ export default function PostEditActions(props: {
 | 
			
		||||
    setUploading(true);
 | 
			
		||||
    const res = await fetch("/api/attachments", {
 | 
			
		||||
      method: "POST",
 | 
			
		||||
      headers: { "Authorization": `Bearer ${getAtk()}` },
 | 
			
		||||
      body: data
 | 
			
		||||
      headers: { Authorization: `Bearer ${getAtk()}` },
 | 
			
		||||
      body: data,
 | 
			
		||||
    });
 | 
			
		||||
    if (res.status !== 200) {
 | 
			
		||||
      props.onError(await res.text());
 | 
			
		||||
@@ -54,10 +64,14 @@ export default function PostEditActions(props: {
 | 
			
		||||
    const form = evt.target as HTMLFormElement;
 | 
			
		||||
    const data = Object.fromEntries(new FormData(form));
 | 
			
		||||
 | 
			
		||||
    setAttachments(attachments().concat([{
 | 
			
		||||
      ...data,
 | 
			
		||||
      author_id: userinfo?.profiles?.id
 | 
			
		||||
    }]));
 | 
			
		||||
    setAttachments(
 | 
			
		||||
      attachments().concat([
 | 
			
		||||
        {
 | 
			
		||||
          ...data,
 | 
			
		||||
          author_id: userinfo?.profiles?.id,
 | 
			
		||||
        },
 | 
			
		||||
      ]),
 | 
			
		||||
    );
 | 
			
		||||
    props.onInputAttachments(attachments());
 | 
			
		||||
    form.reset();
 | 
			
		||||
  }
 | 
			
		||||
@@ -74,10 +88,11 @@ export default function PostEditActions(props: {
 | 
			
		||||
 | 
			
		||||
    const form = evt.target as HTMLFormElement;
 | 
			
		||||
    const data = Object.fromEntries(new FormData(form));
 | 
			
		||||
    if (!data.alias) data.alias = crypto.randomUUID().replace(/-/g, "");
 | 
			
		||||
    if (!data.name) return;
 | 
			
		||||
    if (!data.category) return;
 | 
			
		||||
 | 
			
		||||
    setCategories(categories().concat([data as any]));
 | 
			
		||||
    const item = availableCategories().find((item) => item.alias === data.category);
 | 
			
		||||
 | 
			
		||||
    setCategories(categories().concat([item]));
 | 
			
		||||
    props.onInputCategories(categories());
 | 
			
		||||
    form.reset();
 | 
			
		||||
  }
 | 
			
		||||
@@ -134,19 +149,21 @@ export default function PostEditActions(props: {
 | 
			
		||||
              <span class="label-text">Alias</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <input
 | 
			
		||||
              name="alias" type="text" placeholder="Type here"
 | 
			
		||||
              name="alias"
 | 
			
		||||
              type="text"
 | 
			
		||||
              placeholder="Type here"
 | 
			
		||||
              class="input input-bordered w-full"
 | 
			
		||||
              value={props.editing?.alias ?? ""}
 | 
			
		||||
              onInput={(evt) => props.onInputAlias(evt.target.value)}
 | 
			
		||||
            />
 | 
			
		||||
            <div class="label">
 | 
			
		||||
              <span class="label-text-alt">
 | 
			
		||||
               Leave blank to generate a random string.
 | 
			
		||||
              </span>
 | 
			
		||||
              <span class="label-text-alt">Leave blank to generate a random string.</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </label>
 | 
			
		||||
          <div class="modal-action">
 | 
			
		||||
            <button type="button" class="btn" onClick={() => closeModel("#alias")}>Close</button>
 | 
			
		||||
            <button type="button" class="btn" onClick={() => closeModel("#alias")}>
 | 
			
		||||
              Close
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </dialog>
 | 
			
		||||
@@ -159,7 +176,8 @@ export default function PostEditActions(props: {
 | 
			
		||||
              <span class="label-text">Published At</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <input
 | 
			
		||||
              name="published_at" type="datetime-local"
 | 
			
		||||
              name="published_at"
 | 
			
		||||
              type="datetime-local"
 | 
			
		||||
              placeholder="Pick a date"
 | 
			
		||||
              class="input input-bordered w-full"
 | 
			
		||||
              value={props.editing?.published_at ?? ""}
 | 
			
		||||
@@ -167,13 +185,14 @@ export default function PostEditActions(props: {
 | 
			
		||||
            />
 | 
			
		||||
            <div class="label">
 | 
			
		||||
              <span class="label-text-alt">
 | 
			
		||||
                Before this time, your post will not be visible for everyone.
 | 
			
		||||
                You can modify this plan on Creator Hub.
 | 
			
		||||
                Before this time, your post will not be visible for everyone. You can modify this plan on Creator Hub.
 | 
			
		||||
              </span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </label>
 | 
			
		||||
          <div class="modal-action">
 | 
			
		||||
            <button type="button" class="btn" onClick={() => closeModel("#planning-publish")}>Close</button>
 | 
			
		||||
            <button type="button" class="btn" onClick={() => closeModel("#planning-publish")}>
 | 
			
		||||
              Close
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </dialog>
 | 
			
		||||
@@ -183,10 +202,24 @@ export default function PostEditActions(props: {
 | 
			
		||||
          <h3 class="font-bold text-lg mx-1">Attachments</h3>
 | 
			
		||||
 | 
			
		||||
          <div role="tablist" class="tabs tabs-boxed mt-3">
 | 
			
		||||
            <input type="radio" name="attachment" role="tab" class="tab" aria-label="File picker"
 | 
			
		||||
                   checked={attachmentMode() === 0} onClick={() => setAttachmentMode(0)} />
 | 
			
		||||
            <input type="radio" name="attachment" role="tab" class="tab" aria-label="External link"
 | 
			
		||||
                   checked={attachmentMode() === 1} onClick={() => setAttachmentMode(1)} />
 | 
			
		||||
            <input
 | 
			
		||||
              type="radio"
 | 
			
		||||
              name="attachment"
 | 
			
		||||
              role="tab"
 | 
			
		||||
              class="tab"
 | 
			
		||||
              aria-label="File picker"
 | 
			
		||||
              checked={attachmentMode() === 0}
 | 
			
		||||
              onClick={() => setAttachmentMode(0)}
 | 
			
		||||
            />
 | 
			
		||||
            <input
 | 
			
		||||
              type="radio"
 | 
			
		||||
              name="attachment"
 | 
			
		||||
              role="tab"
 | 
			
		||||
              class="tab"
 | 
			
		||||
              aria-label="External link"
 | 
			
		||||
              checked={attachmentMode() === 1}
 | 
			
		||||
              onClick={() => setAttachmentMode(1)}
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <Switch>
 | 
			
		||||
@@ -197,8 +230,12 @@ export default function PostEditActions(props: {
 | 
			
		||||
                    <span class="label-text">Pick a file</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="join">
 | 
			
		||||
                    <input required type="file" name="attachment"
 | 
			
		||||
                           class="join-item file-input file-input-bordered w-full" />
 | 
			
		||||
                    <input
 | 
			
		||||
                      required
 | 
			
		||||
                      type="file"
 | 
			
		||||
                      name="attachment"
 | 
			
		||||
                      class="join-item file-input file-input-bordered w-full"
 | 
			
		||||
                    />
 | 
			
		||||
                    <button type="submit" class="join-item btn btn-primary" disabled={uploading()}>
 | 
			
		||||
                      <i class="fa-solid fa-upload"></i>
 | 
			
		||||
                    </button>
 | 
			
		||||
@@ -216,14 +253,29 @@ export default function PostEditActions(props: {
 | 
			
		||||
                    <span class="label-text">Attach an external file</span>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="join">
 | 
			
		||||
                    <input required type="text" name="mimetype" class="join-item input input-bordered w-full"
 | 
			
		||||
                           placeholder="Mimetype" />
 | 
			
		||||
                    <input required type="text" name="filename" class="join-item input input-bordered w-full"
 | 
			
		||||
                           placeholder="Name" />
 | 
			
		||||
                    <input
 | 
			
		||||
                      required
 | 
			
		||||
                      type="text"
 | 
			
		||||
                      name="mimetype"
 | 
			
		||||
                      class="join-item input input-bordered w-full"
 | 
			
		||||
                      placeholder="Mimetype"
 | 
			
		||||
                    />
 | 
			
		||||
                    <input
 | 
			
		||||
                      required
 | 
			
		||||
                      type="text"
 | 
			
		||||
                      name="filename"
 | 
			
		||||
                      class="join-item input input-bordered w-full"
 | 
			
		||||
                      placeholder="Name"
 | 
			
		||||
                    />
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div class="join">
 | 
			
		||||
                    <input required type="text" name="external_url" class="join-item input input-bordered w-full"
 | 
			
		||||
                           placeholder="External URL" />
 | 
			
		||||
                    <input
 | 
			
		||||
                      required
 | 
			
		||||
                      type="text"
 | 
			
		||||
                      name="external_url"
 | 
			
		||||
                      class="join-item input input-bordered w-full"
 | 
			
		||||
                      placeholder="External URL"
 | 
			
		||||
                    />
 | 
			
		||||
                    <button type="submit" class="join-item btn btn-primary">
 | 
			
		||||
                      <i class="fa-solid fa-plus"></i>
 | 
			
		||||
                    </button>
 | 
			
		||||
@@ -240,19 +292,23 @@ export default function PostEditActions(props: {
 | 
			
		||||
            <h3 class="font-bold mt-3 mx-1">Attachment list</h3>
 | 
			
		||||
            <ol class="mt-2 mx-1 text-sm">
 | 
			
		||||
              <For each={attachments()}>
 | 
			
		||||
                {(item, idx) => <li>
 | 
			
		||||
                  <i class="fa-regular fa-file me-2"></i>
 | 
			
		||||
                  {item.filename}
 | 
			
		||||
                  <button class="ml-2" onClick={() => removeAttachment(idx())}>
 | 
			
		||||
                    <i class="fa-solid fa-delete-left"></i>
 | 
			
		||||
                  </button>
 | 
			
		||||
                </li>}
 | 
			
		||||
                {(item, idx) => (
 | 
			
		||||
                  <li>
 | 
			
		||||
                    <i class="fa-regular fa-file me-2"></i>
 | 
			
		||||
                    {item.filename}
 | 
			
		||||
                    <button class="ml-2" onClick={() => removeAttachment(idx())}>
 | 
			
		||||
                      <i class="fa-solid fa-delete-left"></i>
 | 
			
		||||
                    </button>
 | 
			
		||||
                  </li>
 | 
			
		||||
                )}
 | 
			
		||||
              </For>
 | 
			
		||||
            </ol>
 | 
			
		||||
          </Show>
 | 
			
		||||
 | 
			
		||||
          <div class="modal-action">
 | 
			
		||||
            <button type="button" class="btn" onClick={() => closeModel("#attachments")}>Close</button>
 | 
			
		||||
            <button type="button" class="btn" onClick={() => closeModel("#attachments")}>
 | 
			
		||||
              Close
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </dialog>
 | 
			
		||||
@@ -266,18 +322,13 @@ export default function PostEditActions(props: {
 | 
			
		||||
                <span class="label-text">Add a category</span>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="join">
 | 
			
		||||
                <input type="text" name="alias" placeholder="Alias" class="join-item input input-bordered w-full" />
 | 
			
		||||
                <input type="text" name="name" placeholder="Name" class="join-item input input-bordered w-full" />
 | 
			
		||||
                <select name="category" class="join-item select select-bordered w-full">
 | 
			
		||||
                  <For each={availableCategories()}>{(item) => <option value={item.alias}>{item.name}</option>}</For>
 | 
			
		||||
                </select>
 | 
			
		||||
                <button type="submit" class="join-item btn btn-primary">
 | 
			
		||||
                  <i class="fa-solid fa-plus"></i>
 | 
			
		||||
                </button>
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="label">
 | 
			
		||||
                <span class="label-text-alt">
 | 
			
		||||
                  Alias is the url key of this category. Lowercase only, required length 4-24.
 | 
			
		||||
                  Leave blank for auto generate.
 | 
			
		||||
                </span>
 | 
			
		||||
              </div>
 | 
			
		||||
            </label>
 | 
			
		||||
          </form>
 | 
			
		||||
 | 
			
		||||
@@ -285,13 +336,15 @@ export default function PostEditActions(props: {
 | 
			
		||||
            <h3 class="font-bold mt-3 mx-1">Category list</h3>
 | 
			
		||||
            <ol class="mt-2 mx-1 text-sm">
 | 
			
		||||
              <For each={categories()}>
 | 
			
		||||
                {(item, idx) => <li>
 | 
			
		||||
                  <i class="fa-solid fa-layer-group me-2"></i>
 | 
			
		||||
                  {item.name} <span class={styles.description}>#{item.alias}</span>
 | 
			
		||||
                  <button class="ml-2" onClick={() => removeCategory(idx())}>
 | 
			
		||||
                    <i class="fa-solid fa-delete-left"></i>
 | 
			
		||||
                  </button>
 | 
			
		||||
                </li>}
 | 
			
		||||
                {(item, idx) => (
 | 
			
		||||
                  <li>
 | 
			
		||||
                    <i class="fa-solid fa-layer-group me-2"></i>
 | 
			
		||||
                    {item.name} <span class={styles.description}>#{item.alias}</span>
 | 
			
		||||
                    <button class="ml-2" onClick={() => removeCategory(idx())}>
 | 
			
		||||
                      <i class="fa-solid fa-delete-left"></i>
 | 
			
		||||
                    </button>
 | 
			
		||||
                  </li>
 | 
			
		||||
                )}
 | 
			
		||||
              </For>
 | 
			
		||||
            </ol>
 | 
			
		||||
          </Show>
 | 
			
		||||
@@ -310,8 +363,7 @@ export default function PostEditActions(props: {
 | 
			
		||||
              </div>
 | 
			
		||||
              <div class="label">
 | 
			
		||||
                <span class="label-text-alt">
 | 
			
		||||
                  Alias is the url key of this tag. Lowercase only, required length 4-24.
 | 
			
		||||
                  Leave blank for auto generate.
 | 
			
		||||
                  Alias is the url key of this tag. Lowercase only, required length 4-24. Leave blank for auto generate.
 | 
			
		||||
                </span>
 | 
			
		||||
              </div>
 | 
			
		||||
            </label>
 | 
			
		||||
@@ -321,22 +373,26 @@ export default function PostEditActions(props: {
 | 
			
		||||
            <h3 class="font-bold mt-3 mx-1">Category list</h3>
 | 
			
		||||
            <ol class="mt-2 mx-1 text-sm">
 | 
			
		||||
              <For each={tags()}>
 | 
			
		||||
                {(item, idx) => <li>
 | 
			
		||||
                  <i class="fa-solid fa-tag me-2"></i>
 | 
			
		||||
                  {item.name} <span class={styles.description}>#{item.alias}</span>
 | 
			
		||||
                  <button class="ml-2" onClick={() => removeTag(idx())}>
 | 
			
		||||
                    <i class="fa-solid fa-delete-left"></i>
 | 
			
		||||
                  </button>
 | 
			
		||||
                </li>}
 | 
			
		||||
                {(item, idx) => (
 | 
			
		||||
                  <li>
 | 
			
		||||
                    <i class="fa-solid fa-tag me-2"></i>
 | 
			
		||||
                    {item.name} <span class={styles.description}>#{item.alias}</span>
 | 
			
		||||
                    <button class="ml-2" onClick={() => removeTag(idx())}>
 | 
			
		||||
                      <i class="fa-solid fa-delete-left"></i>
 | 
			
		||||
                    </button>
 | 
			
		||||
                  </li>
 | 
			
		||||
                )}
 | 
			
		||||
              </For>
 | 
			
		||||
            </ol>
 | 
			
		||||
          </Show>
 | 
			
		||||
 | 
			
		||||
          <div class="modal-action">
 | 
			
		||||
            <button type="button" class="btn" onClick={() => closeModel("#categories-and-tags")}>Close</button>
 | 
			
		||||
            <button type="button" class="btn" onClick={() => closeModel("#categories-and-tags")}>
 | 
			
		||||
              Close
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </dialog>
 | 
			
		||||
    </>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
debug = true
 | 
			
		||||
debug = false
 | 
			
		||||
 | 
			
		||||
name = "Goatplaza"
 | 
			
		||||
maintainer = "SmartSheep Studio"
 | 
			
		||||
@@ -9,8 +9,6 @@ secret = "LtTjzAGFLshwXhN4ZD4nG5KlMv1MWcsvfv03TSZYnT1VhiAnLIZFTnHUwR0XhGgi"
 | 
			
		||||
 | 
			
		||||
content = "uploads"
 | 
			
		||||
 | 
			
		||||
everyone_postable = false
 | 
			
		||||
 | 
			
		||||
[passport]
 | 
			
		||||
client_id = "goatplaza"
 | 
			
		||||
client_secret = "Z9k9AFTj^p"
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user