import BLOG from '@/blog.config' import { NotionAPI } from 'notion-client' import { getDateValue, getTextContent } from 'notion-utils' import formatDate from '../utils/formatDate' // import { createHash } from 'crypto' import md5 from 'js-md5' import { siteConfig } from '../config' import { checkStartWithHttp, convertUrlStartWithOneSlash, getLastSegmentFromUrl } from '../utils' import { extractLangPrefix } from '../utils/pageId' import { mapImgUrl } from './mapImage' import getNotionAPI from '@/lib/notion/getNotionAPI' /** * 获取页面元素成员属性 * @param {*} id * @param {*} value * @param {*} schema * @param {*} authToken * @param {*} tagOptions * @returns */ export default async function getPageProperties( id, value, schema, authToken, tagOptions ) { const rawProperties = Object.entries(value?.properties || []) const excludeProperties = ['date', 'select', 'multi_select', 'person'] const properties = {} for (let i = 0; i < rawProperties.length; i++) { const [key, val] = rawProperties[i] properties.id = id if (schema[key]?.type && !excludeProperties.includes(schema[key].type)) { properties[schema[key].name] = getTextContent(val) } else { switch (schema[key]?.type) { case 'date': { const dateProperty = getDateValue(val) delete dateProperty.type properties[schema[key].name] = dateProperty break } case 'select': case 'multi_select': { const selects = getTextContent(val) if (selects[0]?.length) { properties[schema[key].name] = selects.split(',') } break } case 'person': { const rawUsers = val.flat() const users = [] const api = getNotionAPI() for (let i = 0; i < rawUsers.length; i++) { if (rawUsers[i][0][1]) { const userId = rawUsers[i][0] const res = await api.getUsers(userId) const resValue = res?.recordMapWithRoles?.notion_user?.[userId[1]]?.value const user = { id: resValue?.id, first_name: resValue?.given_name, last_name: resValue?.family_name, profile_photo: resValue?.profile_photo } users.push(user) } } properties[schema[key].name] = users break } default: break } } } // 映射键:用户自定义表头名 const fieldNames = BLOG.NOTION_PROPERTY_NAME if (fieldNames) { Object.keys(fieldNames).forEach(key => { if (fieldNames[key] && properties[fieldNames[key]]) properties[key] = properties[fieldNames[key]] }) } // type\status\category 是单选下拉框 取数组第一个 properties.type = properties.type?.[0] || '' properties.status = properties.status?.[0] || '' properties.category = properties.category?.[0] || '' properties.comment = properties.comment?.[0] || '' // 映射值:用户个性化type和status字段的下拉框选项,在此映射回代码的英文标识 mapProperties(properties) properties.publishDate = new Date( properties?.date?.start_date || value.created_time ).getTime() properties.publishDay = formatDate(properties.publishDate, BLOG.LANG) properties.lastEditedDate = new Date(value?.last_edited_time) properties.lastEditedDay = formatDate( new Date(value?.last_edited_time), BLOG.LANG ) properties.fullWidth = value?.format?.page_full_width ?? false properties.pageIcon = mapImgUrl(value?.format?.page_icon, value) ?? '' properties.pageCover = mapImgUrl(value?.format?.page_cover, value) ?? '' properties.pageCoverThumbnail = mapImgUrl(value?.format?.page_cover, value, 'block') ?? '' properties.ext = convertToJSON(properties?.ext) properties.content = value.content ?? [] properties.tagItems = properties?.tags?.map(tag => { return { name: tag, color: tagOptions?.find(t => t.value === tag)?.color || 'gray' } }) || [] delete properties.content return properties } /** * 字符串转json * @param {*} str * @returns */ function convertToJSON(str) { if (!str) { return {} } // 使用正则表达式去除空格和换行符 try { return JSON.parse(str.replace(/\s/g, '')) } catch (error) { console.warn('无效JSON', str) return {} } } /** * 映射用户自定义表头 */ function mapProperties(properties) { const typeMap = { [BLOG.NOTION_PROPERTY_NAME.type_post]: 'Post', [BLOG.NOTION_PROPERTY_NAME.type_page]: 'Page', [BLOG.NOTION_PROPERTY_NAME.type_notice]: 'Notice', [BLOG.NOTION_PROPERTY_NAME.type_menu]: 'Menu', [BLOG.NOTION_PROPERTY_NAME.type_sub_menu]: 'SubMenu' } const statusMap = { [BLOG.NOTION_PROPERTY_NAME.status_publish]: 'Published', [BLOG.NOTION_PROPERTY_NAME.status_invisible]: 'Invisible' } if (properties?.type && typeMap[properties.type]) { properties.type = typeMap[properties.type] } if (properties?.status && statusMap[properties.status]) { properties.status = statusMap[properties.status] } } /** * 过滤处理页面数据 * 过滤处理过程会用到NOTION_CONFIG中的配置 */ export function adjustPageProperties(properties, NOTION_CONFIG) { // 处理URL // 1.按照用户配置的URL_PREFIX 转换一下slug // 2.为文章添加一个href字段,存储最终调整的路径 if (properties.type === 'Post') { properties.slug = generateCustomizeSlug(properties, NOTION_CONFIG) properties.href = properties.slug ?? properties.id } else if (properties.type === 'Page') { properties.href = properties.slug ?? properties.id } else if (properties.type === 'Menu' || properties.type === 'SubMenu') { // 菜单路径为空、作为可展开菜单使用 properties.href = properties.slug ?? '#' properties.name = properties.title ?? '' } // http or https 开头的视为外链 if (checkStartWithHttp(properties?.href)) { properties.href = properties?.slug properties.target = '_blank' } else { properties.target = '_self' // 伪静态路径右侧拼接.html if (siteConfig('PSEUDO_STATIC', false, NOTION_CONFIG)) { if ( !properties?.href?.endsWith('.html') && properties?.href !== '' && properties?.href !== '#' && properties?.href !== '/' ) { properties.href += '.html' } } // 相对路径转绝对路径:url左侧拼接 / properties.href = convertUrlStartWithOneSlash(properties?.href) } // 如果跳转链接是多语言,则在新窗口打开 if (BLOG.NOTION_PAGE_ID.indexOf(',') > 0) { const siteIds = BLOG.NOTION_PAGE_ID.split(',') for (let index = 0; index < siteIds.length; index++) { const siteId = siteIds[index] const prefix = extractLangPrefix(siteId) if (getLastSegmentFromUrl(properties.href) === prefix) { properties.target = '_blank' } } } // 密码字段md5 properties.password = properties.password ? md5(properties.slug + properties.password) : '' } /** * 获取自定义URL * 可以根据变量生成URL * 支持:%category%/%year%/%month%/%day%/%slug% * @param {*} postProperties * @returns */ function generateCustomizeSlug(postProperties, NOTION_CONFIG) { // 外链不处理 if (checkStartWithHttp(postProperties.slug)) { return postProperties.slug } let fullPrefix = '' let allSlugPatterns = NOTION_CONFIG?.POST_URL_PREFIX if (allSlugPatterns === undefined || allSlugPatterns === null) { allSlugPatterns = siteConfig( 'POST_URL_PREFIX', BLOG.POST_URL_PREFIX, NOTION_CONFIG ).split('/') } else { allSlugPatterns = allSlugPatterns.split('/') } const POST_URL_PREFIX_MAPPING_CATEGORY = siteConfig( 'POST_URL_PREFIX_MAPPING_CATEGORY', {}, NOTION_CONFIG ) allSlugPatterns.forEach((pattern, idx) => { if (pattern === '%year%' && postProperties?.publishDay) { const formatPostCreatedDate = new Date(postProperties?.publishDay) fullPrefix += formatPostCreatedDate.getUTCFullYear() } else if (pattern === '%month%' && postProperties?.publishDay) { const formatPostCreatedDate = new Date(postProperties?.publishDay) fullPrefix += String(formatPostCreatedDate.getUTCMonth() + 1).padStart( 2, 0 ) } else if (pattern === '%day%' && postProperties?.publishDay) { const formatPostCreatedDate = new Date(postProperties?.publishDay) fullPrefix += String(formatPostCreatedDate.getUTCDate()).padStart(2, 0) } else if (pattern === '%slug%') { fullPrefix += postProperties.slug ?? postProperties.id } else if (pattern === '%category%' && postProperties?.category) { let categoryPrefix = postProperties.category // 允许映射分类名,通常用来将中文分类映射成英文,美化url. if (POST_URL_PREFIX_MAPPING_CATEGORY[postProperties?.category]) { categoryPrefix = POST_URL_PREFIX_MAPPING_CATEGORY[postProperties?.category] } fullPrefix += categoryPrefix } else if (!pattern.includes('%')) { fullPrefix += pattern } else { return } if (idx !== allSlugPatterns.length - 1) { fullPrefix += '/' } }) if (fullPrefix.startsWith('/')) { fullPrefix = fullPrefix.substring(1) // 去掉头部的"/" } if (fullPrefix.endsWith('/')) { fullPrefix = fullPrefix.substring(0, fullPrefix.length - 1) // 去掉尾部部的"/" } if (fullPrefix) { return `${fullPrefix}/${postProperties.slug ?? postProperties.id}` } else { return `${postProperties.slug ?? postProperties.id}` } }