Huaiyao Jin

Huaiyao Jin

关于 Notion

为什么用 Notion

最开始想尝试的是 Notion AI,想着把所有记录都放到 Notion 里,能不能通过 AI 发掘一些自己没有意识到的地方,去更好地了解自己,结果效果很一般。

使用三个月后的感受

今年9月初开始正式使用,目前感觉 Notion 有以下一些优点。

记录的内容及我自己的一些使用方法

日常所见所想

把日常的记录、待办事项都放到这个 Memos 的数据库里,用时间线去罗列。可以通过数据库的各种属性去展示和筛选。

快捷输入,主要依赖于 Notion API 及苹果系统的快捷指令。快捷指令参考 https://www.youtube.com/watch?v=mKgi6CWedeg

点击桌面上的 “New Memos”,键盘输入,长句子也可以通过苹果键盘自带的语音输入快速记录。

记录就会插入 Memos 这个数据库。

锁屏界面也可以点击锁屏小组件调用快捷指令来快捷输入。

macOS 上结合 Alfred,可以实现类似手机端的快捷输入。

先准备一个"3 合 1"的快捷指令,然后用 Aflred 的 workflow 去调用它。

Apple Watch上也可以调用快捷指令。

晨间日记

通过日期列的筛选还可以实现"那年今日"的功能。

还可以使用自定义的模板。

年终回顾的时候,可以用特定的筛选条件来过滤今年的以及还没有 review 过的记录。

待办事项

用来取代 Things,让完成的事项也有迹可寻。

每天重复的待办事项罗列得清楚。

通过 Notion 自动化,把完成的项目自动添加到 Memos 数据库。

其他重复的待办事项也一目了然,并且有自动化加持。

完成某一项待办事项的时候点击 Button,可以把当前日期附加到 Records 这一列当中,当作历史记录。同时会根据 Interval 和 Unit 计算下一次待办事项的日期,更新 NextTime 列。例如现在点一下 Button,结果如下:

Mac mini 上设置一个 cronjob 每天去检查 NextTime 列的日期,如果某条事项的日期是当天的话就更新 Text 列为当天的时间。当 Text 列被编辑,又会自动调用 Notion 的自动化去发出一个提醒到 Inbox 里,手机上也会有消息推送。

# Notion update for nexttime - 12/19/2024
30 05 * * * /Users/jinhuaiyao/Dropbox/my_config/Mac_Script/notion_update_for_nexttime.py >> /Users/jinhuaiyao/Log/notion_update_for_nexttime.txt 2>&1

jinhuaiyao@huaiyaos-mac-mini ~ % cat /Users/jinhuaiyao/Dropbox/my_config/Mac_Script/notion_update_for_nexttime.py
#!/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12
import requests
import json
from datetime import datetime, timedelta

# API Token and Database ID
NOTION_TOKEN = "ntn_xxxx"
DATABASE_ID = "13c41d2xxxxx"

# Headers
headers = {
    "Authorization": f"Bearer {NOTION_TOKEN}",
    "Content-Type": "application/json",
    "Notion-Version": "2022-06-28"
}

# Today's date in ISO format
current_date = datetime.utcnow().strftime("%Y-%m-%d")
# UTC 0am, local 8 or 9am
local_am_today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0).isoformat()

# Fetch database items
url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"
response = requests.post(url, headers=headers)
data = response.json()

if "results" not in data:
    print("Error fetching data from Notion:", data)
    exit()

# Iterate over database entries
for page in data["results"]:
    properties = page.get("properties", {})

    # Safely access NextTime property
    next_time_property = properties.get("NextTime", None)
    if next_time_property and isinstance(next_time_property, dict):
        next_time_date = next_time_property.get("date", None)
        if next_time_date and "start" in next_time_date:
            next_time = next_time_date.get("start")
            if next_time == current_date:
                page_id = page["id"]

                # Prepare payload to update Text property
                text_payload = {
                    "properties": {
                        "Text": {
                            "rich_text": [
                                {
                                    "type": "mention",
                                    "mention": {
                                        "type": "date",
                                        "date": {
                                            "start": local_am_today,
                                            "end": None,
                                            "time_zone": None
                                        }
                                    },
                                    "annotations": {
                                        "bold": False,
                                        "italic": False,
                                        "strikethrough": False,
                                        "underline": False,
                                        "code": False,
                                        "color": "default"
                                    },
                                    "plain_text": local_am_today
                                }
                            ]
                        }
                    }
                }

                # Update the page
                update_url = f"https://api.notion.com/v1/pages/{page_id}"
                update_response = requests.patch(update_url, headers=headers, json=text_payload)

                if update_response.status_code == 200:
                    print(f"Successfully updated Text property for page: {page_id}")
                else:
                    print(f"Failed to update page {page_id}:", update_response.json())




jinhuaiyao@huaiyaos-mac-mini ~ % /Users/jinhuaiyao/Dropbox/my_config/Mac_Script/notion_update_for_nexttime.py
Successfully updated Text property for page: 13c41d22-a693-8009-8515-e0ce0d4629f8

效果如下:

等等的每日一照

英文学习

可以标注并且添加注释。

文章剪藏

电脑端 https://www.notion.com/web-clipper

手机端,发送到Notion。

桌面小组件展示

利用 scriptable 这个 app 来调用 notion api 展示记录。

比如即将来临的待办事项显示在手机负一屏。

// 配置参数
max_lines = 15; 

let notionData = await loadDataFromNotion();

// 按照 plan_date 排序
notionData.sort((a, b) => new Date(a.time) - new Date(b.time));

if (config.runsInWidget) {
    let widget = await createWidget(notionData);
    Script.setWidget(widget);
} else if (config.runsWithSiri) {
    let table = createTable(notionData);
    await QuickLook.present(table);
} else {
    let table = createTable(notionData);
    await QuickLook.present(table);
}

Script.complete();

async function createWidget(notionData) {
    // notionData 是一个数组,包含多个 {title, time}

    let gradient = new LinearGradient();
    gradient.locations = [0, 1];
    gradient.colors = [
        new Color("#ffffff80"),
        new Color("#ffffff80")
    ];
    let widget = new ListWidget();
    widget.backgroundColor = new Color("#ffffff80");
    widget.backgroundGradient = gradient;

    // 显示所有记录
    notionData.forEach(data => {
        let timeElement = widget.addText(data.time);
        timeElement.font = Font.mediumSystemFont(12);
        timeElement.textColor = Color.black();
        timeElement.lineLimit = 1;

        let titleElement = widget.addText(data.title);
        titleElement.font = Font.boldSystemFont(16);
        titleElement.textColor = Color.black();
        titleElement.minimumScaleFactor = 0.75;
        titleElement.lineLimit = 1;

        widget.addSpacer(8); // 在标题行下方增加间隔
    });

    return widget;
}

function createTable(notionData) {
    let table = new UITable();

    // 显示所有记录
    notionData.forEach(data => {
        // 时间行
        let row_time = new UITableRow();
        row_time.addText(data.time);
        row_time.cellSpacing = 0; // 消除间隙
        table.addRow(row_time);

        // 标题行
        let row_title = new UITableRow();
        row_title.addText(data.title);
        row_title.cellSpacing = 0; // 消除间隙
        row_title.height = 40; // 增加标题行下方的高度
        table.addRow(row_title);
    });

    return table;
}

async function loadDataFromNotion() {
    let url = 'https://api.notion.com/v1/databases/xxxx/query';
    let req = new Request(url);
    req.method = 'POST';
    req.headers = {
        'Authorization': 'Bearer ntn_xxxx', 
        'Content-Type': 'application/json',
        'Notion-Version': '2022-06-28'
    };

    // 获取当前时间,并格式化为 ISO 8601 字符串
    let currentTime = new Date().toISOString();

    let filterPayload = {
        "filter": {
            "and": [
                {
                    "property": "Tags",
                    "multi_select": {
                        "contains": "Todo"
                    }
                },
                {
                    "property": "PlanDate",
                    "date": {
                        "is_not_empty": true
                    }
                },
                {
                    "property": "Status",
                    "status": {
                        "equals": "Not Started"
                    }
                }
            ]
        }
    };

    req.body = JSON.stringify(filterPayload);
    let json = await req.loadJSON();

    let results = json.results || []; // 确保 results 存在

    // 提取所有符合条件的记录
    let notionData = results.map(result => {
        let title_content = result.properties.Content.title[0].text.content;
        let plan_date_content = result.properties.PlanDate.date.start;

        // 格式化时间为中国时区
        let time = formatTimeToChina(plan_date_content);

        return {
            title: title_content,
            time: time
        };
    });

    return notionData;
}

function formatTimeToChina(time_content) {
    let utc_time = new Date(time_content);

    let options = {
        timeZone: "Asia/Shanghai",
        year: 'numeric', month: '2-digit', day: '2-digit',
        hour: '2-digit', minute: '2-digit', second: '2-digit'
    };

    return utc_time.toLocaleString('zh-CN', options);
}

又比如一些重要的提醒循环显示在手机桌面。

数据同步到 2024 打卡记录

Memos 数据库里特定 tag 的记录定期更新到打卡记录。

# upload heatmap data - 05/07/2024
55 23 * * * /bin/bash /Users/jinhuaiyao/Dropbox/my_config/Mac_Script/upload_heatmap_data.sh >/Users/jinhuaiyao/Log/upload_heatmap_data.txt 2>&1

jinhuaiyao@huaiyaos-mac-mini .tmp % cat /Users/jinhuaiyao/Dropbox/my_config/Mac_Script/upload_heatmap_data.sh
cd /Users/jinhuaiyao/.tmp/

DT=`date +%Y_%m_%d`

for file in `ls *.txt`
do
cp $file /Users/jinhuaiyao/OneDrive/Backup/backup_heatmap/${file}.${DT}
done

bash workout.sh
bash running.sh
bash reading.sh
bash vocabulary.sh
bash englishpod.sh

cd /Users/jinhuaiyao/.tmp/
scp -P 10086 *.txt [email protected]:/home/xx/www/webpage

jinhuaiyao@huaiyaos-mac-mini .tmp % cat workout.sh
/Library/Frameworks/Python.framework/Versions/3.12/bin/python3 workout.py > workout.1

awk -F' ' '{print $1 " |1| " substr($0, index($0,$3))}' workout.1  > workout.2

awk -F'|' '
{
    key = $1 FS $2;
    if (key in data) {
        data[key] = data[key] ", " $3;
    } else {
        data[key] = $3;
    }
}
END {
    for (key in data) {
        print key " |" data[key];
    }
}'  workout.2 |sort >workout.3

cat workout.txt.base workout.3  > workout.txt

jinhuaiyao@huaiyaos-mac-mini .tmp % cat workout.py
import requests
import json
import pytz
from datetime import datetime

# API 令牌
NOTION_TOKEN = "ntn_xxxx"

# 数据库ID
DATABASE_ID = "4bd4xxxx"

# 请求头
headers = {
    "Authorization": f"Bearer {NOTION_TOKEN}",
    "Content-Type": "application/json",
    "Notion-Version": "2022-06-28"
}

# 定义过滤条件,筛选带有 "Workout" 标签的项目
filter_payload = {
    "filter": {
        "property": "Tags",  # 属性名称,假设为 "Tags"
        "multi_select": {
            "contains": "Workout"  # 标签内容
        }
    }
}

# 查询数据库的API URL
url = f"https://api.notion.com/v1/databases/{DATABASE_ID}/query"

# 发送带有过滤器的请求
response = requests.post(url, headers=headers, json=filter_payload)

# 解析并打印响应结果
data = response.json()

# 提取并解码内容
for result in data["results"]:
    # 假设 Content 在 properties 里面,获取 title 属性中的内容
    title_content = result["properties"]["Content"]["title"][0]["text"]["content"]
    time_content = result["properties"]["Time"]["created_time"]
    utc_format = "%Y-%m-%dT%H:%M:%S.%fZ"
    utc_time = datetime.strptime(time_content, utc_format)

		# 设置时区:UTC 和 东八区
    utc = pytz.timezone('UTC')
    cn_tz = pytz.timezone('Asia/Shanghai')
    utc_time = utc.localize(utc_time)
    cn_time = utc_time.astimezone(cn_tz)

    # 打印解码后的标题内容
    print(f"{cn_time}: {title_content}")

jinhuaiyao@huaiyaos-mac-mini .tmp % tail workout.txt
2024-09-23 |1 | 跑步5km
2024-10-02 |1 | 跑步5km
2024-10-03 |1 | 跑步5.7km
2024-10-05 |1 | 跑步6.1km
2024-10-13 |1 | 顾村公园快走
2024-10-20 |1 | 陪等等散步
2024-11-02 |1 | 跳绳
2024-11-09 |1 | 和等等踢球
2024-11-11 |1 | 跳绳300个
2024-11-30 |1 | 跳绳600