mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-02 20:02:25 -06:00 
			
		
		
		
	* [feature] User-selectable preset themes * docs, more theme stuff * lint, tests * fix css name * correct some little issues * add another theme * fix poll background * okay last theme i swear * make retrieval of apimodel themes more conventional * preallocate stylesheet slices
		
			
				
	
	
		
			151 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			151 lines
		
	
	
	
		
			4.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// GoToSocial
 | 
						|
// Copyright (C) GoToSocial Authors admin@gotosocial.org
 | 
						|
// SPDX-License-Identifier: AGPL-3.0-or-later
 | 
						|
//
 | 
						|
// This program is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Affero General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// This program is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
// GNU Affero General Public License for more details.
 | 
						|
//
 | 
						|
// You should have received a copy of the GNU Affero General Public License
 | 
						|
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
package account
 | 
						|
 | 
						|
import (
 | 
						|
	"cmp"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"regexp"
 | 
						|
	"slices"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"codeberg.org/gruf/go-bytesize"
 | 
						|
	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
 | 
						|
	"github.com/superseriousbusiness/gotosocial/internal/config"
 | 
						|
	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
 | 
						|
	"github.com/superseriousbusiness/gotosocial/internal/log"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	themeTitleRegex       = regexp.MustCompile(`(?m)^\ *theme-title:(.*)$`)
 | 
						|
	themeDescriptionRegex = regexp.MustCompile(`(?m)^\ *theme-description:(.*)$`)
 | 
						|
)
 | 
						|
 | 
						|
// GetThemes returns available account css themes.
 | 
						|
func (p *Processor) ThemesGet() []apimodel.Theme {
 | 
						|
	return p.converter.ThemesToAPIThemes(p.themes.SortedByTitle)
 | 
						|
}
 | 
						|
 | 
						|
// Themes represents an in-memory
 | 
						|
// storage structure for themes.
 | 
						|
type Themes struct {
 | 
						|
	// Themes sorted alphabetically
 | 
						|
	// by title (case insensitive).
 | 
						|
	SortedByTitle []*gtsmodel.Theme
 | 
						|
 | 
						|
	// ByFileName contains themes retrievable
 | 
						|
	// by their filename eg., `light-blurple.css`.
 | 
						|
	ByFileName map[string]*gtsmodel.Theme
 | 
						|
}
 | 
						|
 | 
						|
// PopulateThemes parses available account CSS
 | 
						|
// themes from the web assets themes directory.
 | 
						|
func PopulateThemes() *Themes {
 | 
						|
	webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir())
 | 
						|
	if err != nil {
 | 
						|
		log.Panicf(nil, "error getting abs path for web assets: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	themesAbsFilePath := filepath.Join(webAssetsAbsFilePath, "themes")
 | 
						|
	themesFiles, err := os.ReadDir(themesAbsFilePath)
 | 
						|
	if err != nil {
 | 
						|
		log.Warnf(nil, "error reading themes at %s: %v", themesAbsFilePath, err)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	themes := &Themes{
 | 
						|
		ByFileName: make(map[string]*gtsmodel.Theme),
 | 
						|
	}
 | 
						|
 | 
						|
	for _, f := range themesFiles {
 | 
						|
		// Ignore nested directories.
 | 
						|
		if f.IsDir() {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Ignore weird files.
 | 
						|
		info, err := f.Info()
 | 
						|
		if err != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Ignore really big files.
 | 
						|
		if info.Size() > int64(bytesize.MiB) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Get just the name of the
 | 
						|
		// file, eg `blurple-light.css`.
 | 
						|
		fileName := f.Name()
 | 
						|
 | 
						|
		// Get just the `.css` part.
 | 
						|
		extensionWithDot := filepath.Ext(fileName)
 | 
						|
 | 
						|
		// Remove any leading `.`
 | 
						|
		extension := strings.TrimPrefix(extensionWithDot, ".")
 | 
						|
 | 
						|
		// Ignore non-css files.
 | 
						|
		if extension != "css" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Load the file contents.
 | 
						|
		path := filepath.Join(themesAbsFilePath, fileName)
 | 
						|
		contents, err := os.ReadFile(path)
 | 
						|
		if err != nil {
 | 
						|
			log.Warnf(nil, "error reading css theme at %s: %v", path, err)
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		// Try to parse a title and description
 | 
						|
		// for this theme from the file itself.
 | 
						|
		var themeTitle string
 | 
						|
		titleMatches := themeTitleRegex.FindSubmatch(contents)
 | 
						|
		if len(titleMatches) == 2 {
 | 
						|
			themeTitle = strings.TrimSpace(string(titleMatches[1]))
 | 
						|
		} else {
 | 
						|
			// Fall back to file name
 | 
						|
			// without `.css` suffix.
 | 
						|
			themeTitle = strings.TrimSuffix(fileName, ".css")
 | 
						|
		}
 | 
						|
 | 
						|
		var themeDescription string
 | 
						|
		descMatches := themeDescriptionRegex.FindSubmatch(contents)
 | 
						|
		if len(descMatches) == 2 {
 | 
						|
			themeDescription = strings.TrimSpace(string(descMatches[1]))
 | 
						|
		}
 | 
						|
 | 
						|
		theme := >smodel.Theme{
 | 
						|
			Title:       themeTitle,
 | 
						|
			Description: themeDescription,
 | 
						|
			FileName:    fileName,
 | 
						|
		}
 | 
						|
 | 
						|
		themes.SortedByTitle = append(themes.SortedByTitle, theme)
 | 
						|
		themes.ByFileName[fileName] = theme
 | 
						|
	}
 | 
						|
 | 
						|
	// Sort themes alphabetically
 | 
						|
	// by title (case insensitive).
 | 
						|
	slices.SortFunc(themes.SortedByTitle, func(a, b *gtsmodel.Theme) int {
 | 
						|
		return cmp.Compare(strings.ToLower(a.Title), strings.ToLower(b.Title))
 | 
						|
	})
 | 
						|
 | 
						|
	return themes
 | 
						|
}
 |