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