custom emoji
This commit is contained in:
parent
4e711265ff
commit
8f2534568d
@ -158,14 +158,14 @@ function create_tables(): void {
|
||||
// css table
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS css (
|
||||
id INTEGER PRIMARY KEY,
|
||||
filename TEXT NOT NULL,
|
||||
filename TEXT UNIQUE NOT NULL,
|
||||
description TEXT NULL
|
||||
)");
|
||||
|
||||
// mood table
|
||||
$db->exec("CREATE TABLE IF NOT EXISTS mood (
|
||||
id INTEGER PRIMARY KEY,
|
||||
emoji TEXT NOT NULL,
|
||||
emoji TEXT UNIQUE NOT NULL,
|
||||
description TEXT NOT NULL
|
||||
)");
|
||||
} catch (PDOException $e) {
|
||||
|
@ -304,6 +304,58 @@ label.description {
|
||||
outline: 2px solid var(--color-emoji-border);
|
||||
}
|
||||
|
||||
.emoji-checkbox-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: 6px;
|
||||
background-color: var(--color-bg-white);
|
||||
transition: all 0.2s ease;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.emoji-checkbox-item:hover {
|
||||
background-color: var(--color-hover-light);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.emoji-checkbox-item input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.emoji-checkbox-item label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 400;
|
||||
cursor: pointer;
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.emoji-display {
|
||||
font-size: 1.8em;
|
||||
min-width: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.emoji-description {
|
||||
flex-grow: 1;
|
||||
color: var(--color-text-primary);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.emoji-checkbox-item input[type="checkbox"]:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px var(--shadow-primary);
|
||||
}
|
||||
|
||||
/*
|
||||
Responsive layout - adjusts from 1 to 2 columns based on screen width
|
||||
- min-width makes the mobile (stacked) view the default
|
||||
@ -338,10 +390,4 @@ label.description {
|
||||
.file-info {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.form-row .fieldset-items {
|
||||
grid-template-columns: 200px 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
@ -56,10 +56,10 @@ class CssController extends Controller {
|
||||
$this->handleUpload();
|
||||
break;
|
||||
case 'set_theme':
|
||||
$this->handleSetTheme($config);
|
||||
$this->handleSetTheme();
|
||||
break;
|
||||
case 'delete':
|
||||
$this->handleDelete($config);
|
||||
$this->handleDelete();
|
||||
break;
|
||||
}
|
||||
|
||||
@ -67,7 +67,9 @@ class CssController extends Controller {
|
||||
exit;
|
||||
}
|
||||
|
||||
public function handleDelete(ConfigModel $config): void{
|
||||
public function handleDelete(): void{
|
||||
global $config;
|
||||
|
||||
// Don't try to delete the default theme.
|
||||
if (!$_POST['selectCssFile']){
|
||||
http_response_code(400);
|
||||
@ -114,7 +116,9 @@ class CssController extends Controller {
|
||||
$config = $config->save();
|
||||
}
|
||||
|
||||
private function handleSetTheme(ConfigModel $config) {
|
||||
private function handleSetTheme() {
|
||||
global $config;
|
||||
|
||||
if ($_POST['selectCssFile']){
|
||||
// Set custom theme
|
||||
$config->cssId = $_POST['selectCssFile'];
|
||||
|
@ -5,7 +5,7 @@
|
||||
global $user;
|
||||
$view = new MoodView();
|
||||
|
||||
$moodPicker = $view->render_mood_picker(self::get_emojis_with_labels(), $user->mood);
|
||||
$moodPicker = $view->render_mood_picker(self::getEmojisWithLabels(), $user->mood);
|
||||
|
||||
$vars = [
|
||||
'config' => $config,
|
||||
@ -15,7 +15,76 @@
|
||||
$this->render("mood.php", $vars);
|
||||
}
|
||||
|
||||
public function handleMood(){
|
||||
// Shows the custom emoji management page
|
||||
public function showCustomEmoji(){
|
||||
global $config;
|
||||
$emojiList = MoodModel::loadAll();
|
||||
|
||||
$vars = [
|
||||
'config' => $config,
|
||||
'emojiList' => $emojiList,
|
||||
];
|
||||
|
||||
$this->render("emoji.php", $vars);
|
||||
}
|
||||
|
||||
public function handlePost(): void {
|
||||
global $config;
|
||||
|
||||
switch ($_POST['action']) {
|
||||
case 'add':
|
||||
$emoji = trim($_POST['emoji']);
|
||||
$description = trim($_POST['emoji-description']);
|
||||
$this->handleAdd($emoji, $description);
|
||||
break;
|
||||
case 'delete':
|
||||
if (!empty($_POST['delete_emoji_ids'])){
|
||||
$this->handleDelete();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
header('Location: ' . $config->basePath . 'admin/emoji');
|
||||
exit;
|
||||
}
|
||||
|
||||
public function handleAdd(string $emoji, ?string $description=null): void {
|
||||
// Validate 1 visible character in the emoji
|
||||
$charCount = mb_strlen($emoji, 'UTF-8');
|
||||
if ($charCount !== 1) {
|
||||
// TODO - handle error
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate the emoji is actually an emoji
|
||||
$emojiPattern = '/^[\x{1F000}-\x{1F9FF}\x{2600}-\x{26FF}\x{2700}-\x{27BF}\x{1F600}-\x{1F64F}\x{1F300}-\x{1F5FF}\x{1F680}-\x{1F6FF}\x{1F1E0}-\x{1F1FF}\x{1F900}-\x{1F9FF}\x{1FA70}-\x{1FAFF}]$/u';
|
||||
|
||||
if (!preg_match($emojiPattern, $emoji)) {
|
||||
// TODO - handle error
|
||||
return;
|
||||
}
|
||||
|
||||
// emojis should have more bytes than characters
|
||||
$byteCount = strlen($emoji);
|
||||
if ($byteCount <= 1) {
|
||||
// TODO - handle error
|
||||
return;
|
||||
}
|
||||
|
||||
// It looks like an emoji. Let's add it.
|
||||
MoodModel::add($emoji, $description);
|
||||
}
|
||||
|
||||
public function handleDelete(): void {
|
||||
$ids = $_POST['delete_emoji_ids'];
|
||||
|
||||
if (!empty($ids)) {
|
||||
$moodModel = new MoodModel();
|
||||
$moodModel->delete($ids);
|
||||
}
|
||||
}
|
||||
|
||||
public function handleSetMood(){
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['mood'])) {
|
||||
// ensure that the session is valid before proceeding
|
||||
if (!Session::validateCsrfToken($_POST['csrf_token'])) {
|
||||
@ -37,8 +106,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
private static function get_emojis_with_labels(): array {
|
||||
return [
|
||||
private static function getEmojisWithLabels(): array {
|
||||
$customEmoji = MoodModel::loadAll();
|
||||
|
||||
if (!empty($customEmoji)){
|
||||
$custom = [];
|
||||
|
||||
foreach ($customEmoji as $item){
|
||||
$custom[] = [$item['emoji'], $item['description']];
|
||||
}
|
||||
}
|
||||
|
||||
$emoji = [
|
||||
'faces' => [
|
||||
['😀', 'grinning face'],
|
||||
['😄', 'grinning face with smiling eyes'],
|
||||
@ -239,9 +318,14 @@
|
||||
['🛴', 'kick scooter'],
|
||||
['⛵', 'sailboat'],
|
||||
],
|
||||
|
||||
//'custom' => get_user_emojis($db),
|
||||
];
|
||||
|
||||
// add custom emoji if there are any
|
||||
if (isset($custom)){
|
||||
$emoji = ['custom' => $custom] + $emoji;
|
||||
}
|
||||
|
||||
return $emoji;
|
||||
}
|
||||
}
|
||||
?>
|
@ -10,13 +10,15 @@ class Router {
|
||||
['admin', 'AdminController@handleSave', ['POST']],
|
||||
['admin/css', 'CssController'],
|
||||
['admin/css', 'CssController@handlePost', ['POST']],
|
||||
['admin/emoji', 'MoodController@showCustomEmoji'],
|
||||
['admin/emoji', 'MoodController@handlePost', ['POST']],
|
||||
['feed/rss', 'FeedController@rss'],
|
||||
['feed/atom', 'FeedController@atom'],
|
||||
['login', 'AuthController@showLogin'],
|
||||
['login', 'AuthController@handleLogin', ['POST']],
|
||||
['logout', 'AuthController@handleLogout', ['GET', 'POST']],
|
||||
['mood', 'MoodController'],
|
||||
['mood', 'MoodController@handleMood', ['POST']],
|
||||
['mood', 'MoodController@handleSetMood', ['POST']],
|
||||
['setup', 'AdminController@showSetup'],
|
||||
['setup', 'AdminController@handleSetup', ['POST']],
|
||||
['tick/{y}/{m}/{d}/{h}/{i}/{s}', 'TickController'],
|
||||
|
29
src/Model/MoodModel/MoodModel.php
Normal file
29
src/Model/MoodModel/MoodModel.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
// welp this model is overkill
|
||||
class MoodModel{
|
||||
// This isn't memory-efficient,
|
||||
// but I think it'll be fine on this app's scales.
|
||||
public static function loadAll(): array {
|
||||
global $db;
|
||||
|
||||
$stmt = $db->query("SELECT id, emoji, description FROM mood");
|
||||
return $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
// I'm not going to support editing emoji.
|
||||
// It'll just be a delete/readd
|
||||
public static function add(string $emoji, ?string $description): void{
|
||||
global $db;
|
||||
|
||||
$stmt = $db->prepare("INSERT INTO mood (emoji, description) VALUES (?, ?)");
|
||||
$stmt->execute([$emoji, $description]);
|
||||
}
|
||||
|
||||
public static function delete(array $idsToDelete): void{
|
||||
global $db;
|
||||
|
||||
$placeholders = rtrim(str_repeat('?,', count($idsToDelete)), ',');
|
||||
$stmt = $db->prepare("DELETE FROM mood WHERE id IN ($placeholders)");
|
||||
$stmt->execute($idsToDelete);
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
||||
href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath)?>feed/rss/" />
|
||||
<link rel="alternate"
|
||||
type="text/html"
|
||||
href=<?php echo htmlspecialchars($config->baseUrl . $config->basePath) ?> />
|
||||
href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath) ?>" />
|
||||
<description>My tkr</description>
|
||||
<language>en-us</language>
|
||||
<lastBuildDate><?php echo date(DATE_RSS); ?></lastBuildDate>
|
||||
|
47
templates/partials/emoji.php
Normal file
47
templates/partials/emoji.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php /** @var ConfigModel $config */ ?>
|
||||
<?php /** @var array $emojiList */ ?>
|
||||
<h1>Emoji Management</h1>
|
||||
<div>
|
||||
<form action="<?= $config->basePath ?>admin/emoji" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<fieldset>
|
||||
<legend>Add Emoji</legend>
|
||||
<div class="fieldset-items">
|
||||
<label for="emoji">Enter an emoji</label>
|
||||
<input type="text" id="emoji" name="emoji"
|
||||
required maxlength="4" minlength="1"
|
||||
pattern="^[\u{1F000}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F1E0}-\u{1F1FF}\u{1F900}-\u{1F9FF}\u{1FA70}-\u{1FAFF}]$"
|
||||
placeholder="Enter an emoji"
|
||||
>
|
||||
<label for="emoji-description">Description</label>
|
||||
<input type="text" id="emoji-description" name="emoji-description"
|
||||
maxlength="40" minlength="1"
|
||||
placeholder="describe the mood"
|
||||
>
|
||||
<button type="submit" name="action" value="add">Add emoji</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
<?php if (!empty($emojiList)): ?>
|
||||
<form action="<?= $config->basePath ?>admin/emoji" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($_SESSION['csrf_token']) ?>">
|
||||
<fieldset>
|
||||
<legend>Delete Emoji</legend>
|
||||
<div class="fieldset-items">
|
||||
<?php foreach ($emojiList as $emojiItem): ?>
|
||||
<div class="emoji-checkbox-item">
|
||||
<input type="checkbox"
|
||||
id="delete_emoji_<?= htmlspecialchars($emojiItem['id']) ?>"
|
||||
name="delete_emoji_ids[]"
|
||||
value="<?= htmlspecialchars($emojiItem['id']) ?>">
|
||||
<label for="delete_emoji_<?= htmlspecialchars($emojiItem['id']) ?>">
|
||||
<span class="emoji-display"><?= htmlspecialchars($emojiItem['emoji']) ?></span>
|
||||
<span class="emoji-description"><?= htmlspecialchars($emojiItem['description']) ?></span>
|
||||
</label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<button type="submit" name="action" value="delete">Delete selected emoji</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
</div>
|
@ -8,6 +8,7 @@
|
||||
<?php else: ?>
|
||||
<a href="<?= $config->basePath ?>admin">admin</a>
|
||||
<a href="<?= $config->basePath ?>admin/css">css</a>
|
||||
<a href="<?= $config->basePath ?>admin/emoji">emoji</a>
|
||||
<a href="<?= $config->basePath ?>logout">logout</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
Loading…
x
Reference in New Issue
Block a user