わいえむねっと

Contents
Categories
Calendar
2026/11
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Monthly Archives
~2000/01
Recent Entries
RSS1.0
Templates
Information
Processed: 0.050 sec
Chashed: -
2026/09/01 Tue
今日から十徳 本店が営業再開してるはずなので帰りがけに寄ってみる。


酒処十徳
http://www.juttok​u.com/
◎ビルメンテナンスのためお休みしておりました本店は、9月1日より営業を再開いたします。本店休業中は別館をご利用いただき誠にありがとうございました。別館は8月28日をもって営業終了いたしました。





開いてる!




喜楽町にキビナゴの唐揚げ。




ひやおろしのセット。
真ん中の〆張鶴 生原酒ががつんとくる味わい。




亀齢に まぐろ中落ち。


20:34 組長 本店でヤッてんの?
20:36 なや やってる
20:41 組長 30分くらいでつくかな
20:46 なや これんのかい

途中、組長が合流。



  • 滋賀 喜楽町 純米酒 セカンド
  • 秋田 羽州北鹿 純米吟醸 山田錦
  • 新潟 〆張鶴 生原酒
  • 東京 澤乃井 しぼりたて
  • 広島 亀齢 辛口純米 八拾
2026/08/31 Mon
PCを再起動したらHyper-Vゲストで立ち上げていたサービスに繋がらなくなってたのでIPを固定化する。

ホスト(Windows 11)

ゲートウェイを作成する。

  • Hyper-Vマネージャー
  • 仮想スイッチマネージャー → 内部を選択して仮想スイッチの作成
  • 設定
  • ネットワークとインターネット → ネットワークの詳細設定 → (作成したスイッチ) → 追加のプロパティを表示
  • 手動を選択してIPアドレスとサブネットマスクを設定

ゲスト(Ubuntu 26.04)

netplan で設定する。
最初 YAML を手で編集していたけど、コマンドでもいけるようなのでそっちでメモ。

Ubuntu Manpage: netplan-set - write Netplan YAML configuration snippets to file
https://manpages.u​buntu.com/manpages/resolute/man8/netplan-set.8.html​

$ sudo netplan set --origin-hint 99-config ethernets.eth0.addresses="[(ゲストのIPアドレス)/24]"
$ sudo netplan set --origin-hint 99-config ethernets.eth0.routes="[{to: default, via: (ゲートウェイのIPアドレス)}]"
$ sudo netplan set --origin-hint 99-config ethernets.eth0.nameservers.addresses="[8.8.8.8, 8.8.4.4]"

でIPアドレス、ゲートウェイ、DNSを設定。

$ sudo cat /etc/netplan/99-config.yaml
network:
  version: 2
  ethernets:
    eth0:
      addresses:
      - "(ゲストのIPアドレス)/24"
      nameservers:
        addresses:
        - 8.8.8.8
        - 8.8.4.4
      routes:
      - to: "default"
        via: "(ゲートウェイのIPアドレス)"

のようなファイルが生成される。

$ sudo netplan try

で反映。 いつもと違う梅干し買ってみたけどうまいな。
2026/08/30 Sun
日が落ちてからスーパーに行こうとアパートから出たら自転車が二台。
見たことのある形状。
これ、お巡りさんのやつだ。

アパートでなにか?
2026/08/29 Sat
お好み焼きでも焼くか。
2026/08/28 Fri


突発飲みで現場ビルの中華料理屋へ。
ハイボール4杯目で自動的にピッチャーになる。

店員さんには覚えられてるか聞いたら、「覚エテルヨ!前モタクサン飲ンデタネ!」って言われた。
2026/08/27 Thu
まほよの発売日なので秋葉原の書泉に寄って帰り購入。
久しぶりに利用したけど、レジが1階に集約かつセルフレジ導入とかされてたんだ。 ささみ焼きながらささみ食べてる。
2026/08/26 Wed
Teams 死んでるじゃん。
2026/08/25 Tue
高級キーボード「フィルコ」破綻、本業堅調も為替予約失敗引きずる - 日本経済新聞
https://www.nikkei​.com/article/DGXZQOUC17​6M80X10C26​A8000000/

FILCO死んだの?! なんか右耳が痛い。
2026/08/24 Mon
焼鳥重キャンセル。
2026/08/23 Sun

TweetVault④

前回

TweetVault①
TweetVault②
TweetVault③

絞り込み UI の改善

テキストボックスにタグを入力する形で複数条件の絞り込みに対応したけど、タグ一覧から選択するようにしたい。

  • Ctrl を押しながら選択した場合は複数選択にする。
  • 選択済みの場合は解除。
  • 後述の除外選択中のタグを Ctrl で選択した場合は状態を上書き。
  • Alt を押しながら選択した場合は除外するタグの選択として扱う
  • こちらも複数選択可。
  • 選択済みの場合は解除。
  • 通常選択中のタグを Alt で選択した場合は状態を上書き。
  • 通常選択のハイライトが青なのに対して、こちらは赤にする。
  • Ctrl も Alt も押下していない場合は従前の動作で排他選択。
  • 複数選択や除外選択されているタグがある場合は解除する。

frontend/src/app/page.tsx
@@ -17,3 +17,3 @@
   const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
-  const [selectedTag, setSelectedTag] = useState<string>();
+  const [selectedTag, setSelectedTag] = useState({inc:[], exc:[]});
   const [searchQuery, setSearchQuery] = useState('');
@@ -54,3 +54,3 @@
       const data = await api.getBookmarks({
-        tag: selectedTag,
+        tag: JSON.stringify(selectedTag),
         search: searchQuery,
@@ -117,4 +117,26 @@
 
-  function handleTagSelect(tag: string | undefined) {
-    setSelectedTag(tag);
+  function handleTagSelect(tag: string | undefined, e?: any) {
+    if (!tag) {
+      setSelectedTag({inc:[], exc:[]});
+      return;
+    }
+    setSelectedTag(prev => {
+      if (e && e.ctrlKey) {
+        return {
+          inc: prev.inc.includes(tag)
+             ? prev.inc.filter(item => item !== tag)
+             : [...prev.inc, tag],
+          exc: prev.exc.filter(item => item !== tag)
+        };
+      } else if (e && e.altKey) {
+        return {
+          exc: prev.exc.includes(tag)
+             ? prev.exc.filter(item => item !== tag)
+             : [...prev.exc, tag],
+          inc: prev.inc.filter(item => item !== tag)
+        };
+      } else {
+        return {inc:[tag], exc:[]};
+      }
+    });
     setCurrentPage(1);
選択中のタグを保持する selectedTag を、文字列から対象/対象外リストを保持するオブジェクトに変更。
Ctrl/Alt 押下時の処理を追加。

frontend/src/components/bookmarks/SearchAndFilter.tsx
@@ -8,3 +8,3 @@
   onTagSelect: (tag: string | undefined) => void;
-  selectedTag?: string;
+  selectedTag?: {};
   onDeleteTag?: (tagName: string) => void;
@@ -88,3 +88,3 @@
       // If the deleted tag was selected, reset to show all bookmarks
-      if (selectedTag === tagName) {
+      if (selectedTag.inc.includes(tagName) || selectedTag.exc.includes(tagName)) {
         onTagSelect(undefined);
@@ -122,3 +122,3 @@
             className={`rounded-full px-3 py-1 text-sm whitespace-nowrap ${
-              !selectedTag
+              selectedTag.inc.length == 0 && selectedTag.exc.length == 0
                 ? 'bg-blue-600 text-white'
@@ -132,6 +132,6 @@
               <div
-                onClick={() => onTagSelect(tag.name)}
+                onClick={(e) => onTagSelect(tag.name, e)}
                 className={`px-3 py-1 text-sm whitespace-nowrap flex items-center cursor-pointer ${
-                  selectedTag === tag.name
-                    ? 'bg-blue-600 text-white'
+                  selectedTag.inc.includes(tag.name) ? 'bg-blue-600 text-white' :
+                  selectedTag.exc.includes(tag.name) ? 'bg-red-600 text-white'
                     : 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 hover:bg-gray-200 dark:hover:bg-gray-600'
選択中タグに関する判定を修正。
キーボードイベントの引き渡しを追加。

frontend/src/components/bookmarks/TagMenu.tsx
@@ -8,3 +8,3 @@
   onSuccess: () => void;
-  selectedTag?: string;
+  selectedTag?: {};
   onDeleteTag?: (tagName: string) => void;
@@ -134,3 +134,4 @@
           className={`p-1 rounded-full cursor-pointer ${
-            selectedTag === tag.name ? 'hover:bg-blue-700' : 'text-gray-500 hover:bg-gray-200'
+            selectedTag.inc.includes(tag.name) ? 'hover:bg-blue-700' :
+            selectedTag.exc.includes(tag.name) ? 'hover:bg-red-700' : 'text-gray-500 hover:bg-gray-200'
           }`}
選択中タグに関する判定を修正。

backend/internal/services/bookmark_service.go(前回からの差分)
@@ -4,3 +4,2 @@
     "encoding/json"
-    "fmt"
     "log"
@@ -8,2 +7,3 @@
     "strings"
+    "slices"
 
@@ -197,6 +197,9 @@
 
-    type Tag struct {
+    type RequestTag struct {
+        Include []string `json:"inc"`
+        Exclude []string `json:"exc"`
+    }
+    type QueryTag struct {
         Name string `json:"name"`
     }
-    var tags []Tag
 
@@ -208,6 +211,24 @@
     // Apply tag filter if provided
-    if tag == "untagged" {
-        query = query.Where("tags_json::jsonb = ?", "[]")
-    } else if tag != "" {
-        tags = append(tags, Tag{Name: tag})
+    if tag != "" {
+        var req RequestTag
+        if err := json.Unmarshal([]byte(tag), &req); err != nil {
+            return nil, 0, err
+        }
+        if slices.Contains(req.Include, "untagged") {
+            query = query.Where("tags_json::jsonb = ?", "[]")
+        } else {
+            if len(req.Include) != 0 {
+                var tags []QueryTag
+                for _, v := range req.Include {
+                    tags = append(tags, QueryTag{Name: v})
+                }
+                tagsJson, _ := json.Marshal(tags)
+                query = query.Where("tags_json::jsonb @> ?", tagsJson)
+            }
+            for _, v := range req.Exclude {
+                tags := []QueryTag{{Name: v}}
+                tagsJson, _ := json.Marshal(tags)
+                query = query.Not("tags_json::jsonb @> ?", tagsJson)
+            }
+        }
     }
@@ -218,18 +239,5 @@
         for _, word := range words {
-            if strings.HasPrefix(word, "#") {
-                tags = append(tags, Tag{Name: strings.TrimPrefix(word, "#")})
-            } else if strings.HasPrefix(word, "-#") {
-                query = query.Not("tags_json::jsonb @> ?", fmt.Sprintf(`[{"name":"%s"}]`, strings.TrimPrefix(word, "-#")))
-            } else {
-                query = query.Where("full_text ILIKE ? OR name ILIKE ? OR screen_name ILIKE ?",
-                "%"+word+"%", "%"+word+"%", "%"+word+"%")
-            }
-        }
-    }
-    if len(tags) != 0 {
-        jsonData, err := json.Marshal(tags)
-        if err != nil {
-            return nil, 0, err
+            query = query.Where("full_text ILIKE ? OR name ILIKE ? OR screen_name ILIKE ?",
+            "%"+word+"%", "%"+word+"%", "%"+word+"%")
         }
-        query = query.Where("tags_json::jsonb @> ?", jsonData)
     }
フロントエンドから渡されたタグのリストから検索するよう改修。
テキストボックスの入力からタグ検索する処理は除去。複数キーワード対応は残す。
元の処理に合わせるためにいったんアンマーシャルしているのが微妙。