custom emoji
This commit is contained in:
parent
4e711265ff
commit
8f2534568d
@ -158,14 +158,14 @@ function create_tables(): void {
|
|||||||
// css table
|
// css table
|
||||||
$db->exec("CREATE TABLE IF NOT EXISTS css (
|
$db->exec("CREATE TABLE IF NOT EXISTS css (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
filename TEXT NOT NULL,
|
filename TEXT UNIQUE NOT NULL,
|
||||||
description TEXT NULL
|
description TEXT NULL
|
||||||
)");
|
)");
|
||||||
|
|
||||||
// mood table
|
// mood table
|
||||||
$db->exec("CREATE TABLE IF NOT EXISTS mood (
|
$db->exec("CREATE TABLE IF NOT EXISTS mood (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
emoji TEXT NOT NULL,
|
emoji TEXT UNIQUE NOT NULL,
|
||||||
description TEXT NOT NULL
|
description TEXT NOT NULL
|
||||||
)");
|
)");
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
|
@ -304,6 +304,58 @@ label.description {
|
|||||||
outline: 2px solid var(--color-emoji-border);
|
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
|
Responsive layout - adjusts from 1 to 2 columns based on screen width
|
||||||
- min-width makes the mobile (stacked) view the default
|
- min-width makes the mobile (stacked) view the default
|
||||||
@ -338,10 +390,4 @@ label.description {
|
|||||||
.file-info {
|
.file-info {
|
||||||
grid-column: 2;
|
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();
|
$this->handleUpload();
|
||||||
break;
|
break;
|
||||||
case 'set_theme':
|
case 'set_theme':
|
||||||
$this->handleSetTheme($config);
|
$this->handleSetTheme();
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
$this->handleDelete($config);
|
$this->handleDelete();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +67,9 @@ class CssController extends Controller {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleDelete(ConfigModel $config): void{
|
public function handleDelete(): void{
|
||||||
|
global $config;
|
||||||
|
|
||||||
// Don't try to delete the default theme.
|
// Don't try to delete the default theme.
|
||||||
if (!$_POST['selectCssFile']){
|
if (!$_POST['selectCssFile']){
|
||||||
http_response_code(400);
|
http_response_code(400);
|
||||||
@ -114,7 +116,9 @@ class CssController extends Controller {
|
|||||||
$config = $config->save();
|
$config = $config->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleSetTheme(ConfigModel $config) {
|
private function handleSetTheme() {
|
||||||
|
global $config;
|
||||||
|
|
||||||
if ($_POST['selectCssFile']){
|
if ($_POST['selectCssFile']){
|
||||||
// Set custom theme
|
// Set custom theme
|
||||||
$config->cssId = $_POST['selectCssFile'];
|
$config->cssId = $_POST['selectCssFile'];
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
global $user;
|
global $user;
|
||||||
$view = new MoodView();
|
$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 = [
|
$vars = [
|
||||||
'config' => $config,
|
'config' => $config,
|
||||||
@ -15,7 +15,76 @@
|
|||||||
$this->render("mood.php", $vars);
|
$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'])) {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' and isset($_POST['mood'])) {
|
||||||
// ensure that the session is valid before proceeding
|
// ensure that the session is valid before proceeding
|
||||||
if (!Session::validateCsrfToken($_POST['csrf_token'])) {
|
if (!Session::validateCsrfToken($_POST['csrf_token'])) {
|
||||||
@ -37,8 +106,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function get_emojis_with_labels(): array {
|
private static function getEmojisWithLabels(): array {
|
||||||
return [
|
$customEmoji = MoodModel::loadAll();
|
||||||
|
|
||||||
|
if (!empty($customEmoji)){
|
||||||
|
$custom = [];
|
||||||
|
|
||||||
|
foreach ($customEmoji as $item){
|
||||||
|
$custom[] = [$item['emoji'], $item['description']];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$emoji = [
|
||||||
'faces' => [
|
'faces' => [
|
||||||
['😀', 'grinning face'],
|
['😀', 'grinning face'],
|
||||||
['😄', 'grinning face with smiling eyes'],
|
['😄', 'grinning face with smiling eyes'],
|
||||||
@ -239,9 +318,14 @@
|
|||||||
['🛴', 'kick scooter'],
|
['🛴', 'kick scooter'],
|
||||||
['⛵', 'sailboat'],
|
['⛵', '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', 'AdminController@handleSave', ['POST']],
|
||||||
['admin/css', 'CssController'],
|
['admin/css', 'CssController'],
|
||||||
['admin/css', 'CssController@handlePost', ['POST']],
|
['admin/css', 'CssController@handlePost', ['POST']],
|
||||||
|
['admin/emoji', 'MoodController@showCustomEmoji'],
|
||||||
|
['admin/emoji', 'MoodController@handlePost', ['POST']],
|
||||||
['feed/rss', 'FeedController@rss'],
|
['feed/rss', 'FeedController@rss'],
|
||||||
['feed/atom', 'FeedController@atom'],
|
['feed/atom', 'FeedController@atom'],
|
||||||
['login', 'AuthController@showLogin'],
|
['login', 'AuthController@showLogin'],
|
||||||
['login', 'AuthController@handleLogin', ['POST']],
|
['login', 'AuthController@handleLogin', ['POST']],
|
||||||
['logout', 'AuthController@handleLogout', ['GET', 'POST']],
|
['logout', 'AuthController@handleLogout', ['GET', 'POST']],
|
||||||
['mood', 'MoodController'],
|
['mood', 'MoodController'],
|
||||||
['mood', 'MoodController@handleMood', ['POST']],
|
['mood', 'MoodController@handleSetMood', ['POST']],
|
||||||
['setup', 'AdminController@showSetup'],
|
['setup', 'AdminController@showSetup'],
|
||||||
['setup', 'AdminController@handleSetup', ['POST']],
|
['setup', 'AdminController@handleSetup', ['POST']],
|
||||||
['tick/{y}/{m}/{d}/{h}/{i}/{s}', 'TickController'],
|
['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/" />
|
href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath)?>feed/rss/" />
|
||||||
<link rel="alternate"
|
<link rel="alternate"
|
||||||
type="text/html"
|
type="text/html"
|
||||||
href=<?php echo htmlspecialchars($config->baseUrl . $config->basePath) ?> />
|
href="<?php echo htmlspecialchars($config->baseUrl . $config->basePath) ?>" />
|
||||||
<description>My tkr</description>
|
<description>My tkr</description>
|
||||||
<language>en-us</language>
|
<language>en-us</language>
|
||||||
<lastBuildDate><?php echo date(DATE_RSS); ?></lastBuildDate>
|
<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: ?>
|
<?php else: ?>
|
||||||
<a href="<?= $config->basePath ?>admin">admin</a>
|
<a href="<?= $config->basePath ?>admin">admin</a>
|
||||||
<a href="<?= $config->basePath ?>admin/css">css</a>
|
<a href="<?= $config->basePath ?>admin/css">css</a>
|
||||||
|
<a href="<?= $config->basePath ?>admin/emoji">emoji</a>
|
||||||
<a href="<?= $config->basePath ?>logout">logout</a>
|
<a href="<?= $config->basePath ?>logout">logout</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
Loading…
x
Reference in New Issue
Block a user