commit
526254885c
9 changed files with 3498 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||||||
|
MIRO_APP_ID= |
||||||
|
MIRO_TOKEN= |
||||||
|
MIRO_BOARD= |
||||||
|
NOTION_PAGE= |
||||||
|
NOTION_TOKEN= |
||||||
|
VERBOSE=0 |
||||||
|
DATABASE_SYNC_PAGE_COUNT=0 |
||||||
|
SYNCED_PROPERTY=Miro Name,Equipment,Scenario,No. |
||||||
|
FILTER={"filter":{"or":[{"property": "UC scope", "select": {"equals": "A"}},{"property": "UC scope","select": {"equals": "B"}}]}} |
||||||
@ -0,0 +1,119 @@ |
|||||||
|
exe |
||||||
|
db.json |
||||||
|
|
||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html) |
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
||||||
|
|
||||||
|
# Runtime data |
||||||
|
pids |
||||||
|
*.pid |
||||||
|
*.seed |
||||||
|
*.pid.lock |
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover |
||||||
|
lib-cov |
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul |
||||||
|
coverage |
||||||
|
*.lcov |
||||||
|
|
||||||
|
# nyc test coverage |
||||||
|
.nyc_output |
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
||||||
|
.grunt |
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/) |
||||||
|
bower_components |
||||||
|
|
||||||
|
# node-waf configuration |
||||||
|
.lock-wscript |
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html) |
||||||
|
build/Release |
||||||
|
|
||||||
|
# Dependency directories |
||||||
|
node_modules/ |
||||||
|
jspm_packages/ |
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/) |
||||||
|
web_modules/ |
||||||
|
|
||||||
|
# TypeScript cache |
||||||
|
*.tsbuildinfo |
||||||
|
|
||||||
|
# Optional npm cache directory |
||||||
|
.npm |
||||||
|
|
||||||
|
# Optional eslint cache |
||||||
|
.eslintcache |
||||||
|
|
||||||
|
# Microbundle cache |
||||||
|
.rpt2_cache/ |
||||||
|
.rts2_cache_cjs/ |
||||||
|
.rts2_cache_es/ |
||||||
|
.rts2_cache_umd/ |
||||||
|
|
||||||
|
# Optional REPL history |
||||||
|
.node_repl_history |
||||||
|
|
||||||
|
# Output of 'npm pack' |
||||||
|
*.tgz |
||||||
|
|
||||||
|
# Yarn Integrity file |
||||||
|
.yarn-integrity |
||||||
|
|
||||||
|
# dotenv environment variables file |
||||||
|
.env |
||||||
|
.env.test |
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/) |
||||||
|
.cache |
||||||
|
.parcel-cache |
||||||
|
|
||||||
|
# Next.js build output |
||||||
|
.next |
||||||
|
out |
||||||
|
|
||||||
|
# Nuxt.js build / generate output |
||||||
|
.nuxt |
||||||
|
dist |
||||||
|
|
||||||
|
# Gatsby files |
||||||
|
.cache/ |
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js |
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support |
||||||
|
# public |
||||||
|
|
||||||
|
# vuepress build output |
||||||
|
.vuepress/dist |
||||||
|
|
||||||
|
# Serverless directories |
||||||
|
.serverless/ |
||||||
|
|
||||||
|
# FuseBox cache |
||||||
|
.fusebox/ |
||||||
|
|
||||||
|
# DynamoDB Local files |
||||||
|
.dynamodb/ |
||||||
|
|
||||||
|
# TernJS port file |
||||||
|
.tern-port |
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions |
||||||
|
.vscode-test |
||||||
|
|
||||||
|
# yarn v2 |
||||||
|
.yarn/cache |
||||||
|
.yarn/unplugged |
||||||
|
.yarn/build-state.yml |
||||||
|
.yarn/install-state.gz |
||||||
|
.pnp.* |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
// https://developers.miro.com/reference#board-object
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
|
||||||
|
type WidgetType = 'sticker' | 'shape' | 'text' | 'image' | 'webscreenshot' | 'document' | 'paint' | 'preview' | 'embed' | 'mockup' | 'line' | 'frame' | 'card' | 'kanban' | 'usm'; |
||||||
|
type ShapeType = string; |
||||||
|
|
||||||
|
interface Style { |
||||||
|
backgroundColor: string; |
||||||
|
borderColor: string; |
||||||
|
shapeType: string; |
||||||
|
} |
||||||
|
|
||||||
|
interface MetaData { |
||||||
|
[key: string]: any; |
||||||
|
} |
||||||
|
|
||||||
|
// https://developers.miro.com/reference#widget-object
|
||||||
|
interface MiroWidget { |
||||||
|
id: string; |
||||||
|
style: Style; |
||||||
|
type: WidgetType; |
||||||
|
x: number; |
||||||
|
y: number; |
||||||
|
height: number; |
||||||
|
width: number; |
||||||
|
metadata?: MetaData; |
||||||
|
} |
||||||
|
|
||||||
|
interface MiroWidgetText extends MiroWidget { |
||||||
|
text: string; |
||||||
|
} |
||||||
|
|
||||||
|
interface MiroWidgetCard extends MiroWidget { |
||||||
|
title: string; |
||||||
|
} |
||||||
|
|
||||||
|
interface Collection { |
||||||
|
type: "collection"; |
||||||
|
data: MiroWidget[]; |
||||||
|
size: number; |
||||||
|
} |
||||||
@ -0,0 +1,109 @@ |
|||||||
|
import got from 'got'; |
||||||
|
import dotenv from 'dotenv'; |
||||||
|
dotenv.config(); |
||||||
|
|
||||||
|
let miro_app_id = process.env.MIRO_APP_ID || ""; |
||||||
|
let miro_token = process.env.MIRO_TOKEN || ""; |
||||||
|
let miro_board = process.env.MIRO_BOARD || ""; |
||||||
|
|
||||||
|
let miroCreateWidgetUrl = `https://api.miro.com/v1/boards/${miro_board}/widgets`; |
||||||
|
export async function CreateCard(title: string, metaData?: MetaData, x?: number, y?: number): Promise<MiroWidgetCard> { |
||||||
|
let metadata: undefined | MetaData = undefined; |
||||||
|
if (metaData) { |
||||||
|
metadata = {}; |
||||||
|
metadata[miro_app_id] = metaData; |
||||||
|
}; |
||||||
|
return await got.post<MiroWidgetCard>(miroCreateWidgetUrl, { |
||||||
|
headers: { Authorization: `Bearer ${miro_token}` }, |
||||||
|
json: { "type": "card", "title": title, metadata, x, y }, |
||||||
|
responseType: 'json', |
||||||
|
resolveBodyOnly: true |
||||||
|
}); |
||||||
|
// console.log(JSON.stringify({ "type": "card", "title": title, metadata }));
|
||||||
|
// console.log(response.body);
|
||||||
|
} |
||||||
|
|
||||||
|
export async function CreateText(title: string, metaData?: MetaData, x?: number, y?: number, callback?: MiroRateLimitingCallbackType): Promise<MiroWidgetText> { |
||||||
|
let metadata: undefined | MetaData = undefined; |
||||||
|
if (metaData) { |
||||||
|
metadata = {}; |
||||||
|
metadata[miro_app_id] = metaData; |
||||||
|
}; |
||||||
|
let result = await got.post<MiroWidgetText>(miroCreateWidgetUrl, { |
||||||
|
headers: { Authorization: `Bearer ${miro_token}` }, |
||||||
|
json: { "type": "text", "text": title, metadata, x, y }, |
||||||
|
responseType: 'json' |
||||||
|
}); |
||||||
|
callback && callback(Number(result.headers["x-ratelimit-reset"]), Number(result.headers["x-ratelimit-remaining"])); |
||||||
|
return result.body; |
||||||
|
// console.log(response.body);
|
||||||
|
} |
||||||
|
|
||||||
|
// (async () => {
|
||||||
|
// let card = await CreateCard('simple card 3', { notionId: "2" });
|
||||||
|
// console.log(JSON.stringify(card));
|
||||||
|
// card = await CreateCard('simple card 3');
|
||||||
|
// console.log(JSON.stringify(card));
|
||||||
|
// })();
|
||||||
|
|
||||||
|
type MiroRateLimitingCallbackType = (rateLimitReset: number, rateLimitRemaining: number) => void; |
||||||
|
|
||||||
|
let miroUpdateWidgetUrl = (widget_id: string) => `https://api.miro.com/v1/boards/${miro_board}/widgets/${widget_id}`; |
||||||
|
export async function UpdateCard(widget_id: string, title: string, metaData?: MetaData, callback?: MiroRateLimitingCallbackType): Promise<MiroWidgetCard> { |
||||||
|
let metadata: undefined | MetaData = undefined; |
||||||
|
if (metaData) { |
||||||
|
metadata = {}; |
||||||
|
metadata[miro_app_id] = metaData; |
||||||
|
}; |
||||||
|
let result = await got.patch<MiroWidgetCard>(miroUpdateWidgetUrl(widget_id), { |
||||||
|
headers: { Authorization: `Bearer ${miro_token}` }, |
||||||
|
json: { "title": title, metadata }, |
||||||
|
responseType: 'json' |
||||||
|
}); |
||||||
|
callback && callback(Number(result.headers["x-ratelimit-reset"]), Number(result.headers["x-ratelimit-remaining"])); |
||||||
|
return result.body; |
||||||
|
} |
||||||
|
|
||||||
|
export async function UpdateText(widget_id: string, title: string, metaData?: MetaData, callback?: MiroRateLimitingCallbackType): Promise<MiroWidgetText> { |
||||||
|
let metadata: undefined | MetaData = undefined; |
||||||
|
if (metaData) { |
||||||
|
metadata = {}; |
||||||
|
metadata[miro_app_id] = metaData; |
||||||
|
}; |
||||||
|
let result = await got.patch<MiroWidgetText>(miroUpdateWidgetUrl(widget_id), { |
||||||
|
headers: { Authorization: `Bearer ${miro_token}` }, |
||||||
|
json: { "text": title, metadata }, |
||||||
|
responseType: 'json' |
||||||
|
}); |
||||||
|
// clg
|
||||||
|
// if (callback) {
|
||||||
|
// console.log('rate limit callback: ', Number(result.headers["x-ratelimit-reset"]), Number(result.headers["x-ratelimit-remaining"]));
|
||||||
|
callback && callback(Number(result.headers["x-ratelimit-reset"]), Number(result.headers["x-ratelimit-remaining"])); |
||||||
|
// }
|
||||||
|
return result.body; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
let miroListWidgetUrl = `https://api.miro.com/v1/boards/${miro_board}/widgets/` |
||||||
|
export async function GetAllWidgets(): Promise<Collection> { |
||||||
|
return await got<Collection>(miroListWidgetUrl, { |
||||||
|
headers: { Authorization: `Bearer ${miro_token}` }, |
||||||
|
responseType: 'json', |
||||||
|
resolveBodyOnly: true |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export async function GetAllTexts(): Promise<Collection> { |
||||||
|
return await got<Collection>(miroListWidgetUrl + "?widgetType=text", { |
||||||
|
headers: { Authorization: `Bearer ${miro_token}` }, |
||||||
|
responseType: 'json', |
||||||
|
resolveBodyOnly: true |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// (async () => {
|
||||||
|
// let collection = await GetAllWidgets();
|
||||||
|
// collection && collection.data
|
||||||
|
// .map((widget => ({ id: widget.id, type: widget.type, metadata: widget.metadata })))
|
||||||
|
// .forEach(c => console.log(c.id, c.type, c.metadata));
|
||||||
|
// })();
|
||||||
@ -0,0 +1,95 @@ |
|||||||
|
import { Block, PropertyValue, StringFormulaValue } from "@notionhq/client/build/src/api-types"; |
||||||
|
|
||||||
|
export function isEmptyOrSpaces(value: string) { |
||||||
|
return (!value || value == undefined || value == "" || value.length == 0); |
||||||
|
} |
||||||
|
|
||||||
|
export function ProcessNotionBlock(blocks: Block[]) { |
||||||
|
return blocks.map(block => { |
||||||
|
switch (block.type) { |
||||||
|
case "paragraph": return { id: block.id, richText: block[block.type].text }; |
||||||
|
case 'heading_1': return { id: block.id, richText: block[block.type].text }; |
||||||
|
case 'heading_2': return { id: block.id, richText: block[block.type].text }; |
||||||
|
case 'heading_3': return { id: block.id, richText: block[block.type].text }; |
||||||
|
case 'bulleted_list_item': return { id: block.id, richText: block[block.type].text }; |
||||||
|
case 'numbered_list_item': return { id: block.id, richText: block[block.type].text }; |
||||||
|
case 'to_do': return { id: block.id, richText: block[block.type].text }; |
||||||
|
case 'toggle': return { id: block.id, richText: block[block.type].text }; |
||||||
|
case 'child_page': { |
||||||
|
let title = block[block.type].title; |
||||||
|
title && console.warn(`not handling child page: ${title}`); |
||||||
|
return null; |
||||||
|
// SyncInfo(result.id, `# ${prettyText}`);
|
||||||
|
} |
||||||
|
case 'unsupported': { |
||||||
|
return { id: block.id, richText: null }; |
||||||
|
// await SyncNotionDatabase(block.id, miroWidgetInfo);
|
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export function ProcessNotionProperty(property: [key: string, value: PropertyValue][]) { |
||||||
|
return property.map(([key, value]) => { |
||||||
|
switch (value.type) { |
||||||
|
case "title": return { id: value.id, richText: value[value.type], name: key }; |
||||||
|
case 'rich_text': return { id: value.id, richText: value[value.type], name: key }; |
||||||
|
case 'select': return { id: value.id, text: value[value.type].name, name: key }; |
||||||
|
case 'formula': return { |
||||||
|
id: value.id, |
||||||
|
text: (value.formula as StringFormulaValue).string, |
||||||
|
name: key |
||||||
|
}; |
||||||
|
default: return null; |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
export type MiroWidgetInfo = { |
||||||
|
id: string, |
||||||
|
type: string |
||||||
|
}; |
||||||
|
|
||||||
|
export interface MiroSyncInfo { |
||||||
|
[notionPageId: string]: { |
||||||
|
[notionPropertyId: string]: MiroWidgetInfo[] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export function ProcessMiroWidget(data: MiroWidget[], miro_app_id: string) { |
||||||
|
let info: MiroSyncInfo = {}; |
||||||
|
data.forEach(w => { |
||||||
|
if (w.metadata == null) return; |
||||||
|
if (w.metadata[miro_app_id] == null) return; |
||||||
|
|
||||||
|
// console.log(w.metadata[miro_app_id]);
|
||||||
|
|
||||||
|
let notionPageId = w.metadata[miro_app_id].notionPageId as string; |
||||||
|
let notionPropertyId = w.metadata[miro_app_id].notionPropertyId as string; |
||||||
|
|
||||||
|
let obj = { id: w.id, type: w.type }; |
||||||
|
|
||||||
|
if (notionPageId === undefined) return; |
||||||
|
|
||||||
|
if (info[notionPageId] === undefined) |
||||||
|
info[notionPageId] = {}; |
||||||
|
|
||||||
|
if (notionPropertyId !== undefined) { |
||||||
|
if (info[notionPageId][notionPropertyId] !== undefined) |
||||||
|
info[notionPageId][notionPropertyId] = [...info[notionPageId][notionPropertyId], obj]; |
||||||
|
else |
||||||
|
info[notionPageId][notionPropertyId] = [obj]; |
||||||
|
} |
||||||
|
else { |
||||||
|
if (info[notionPageId]["_"] !== undefined) |
||||||
|
info[notionPageId]["_"] = [...info[notionPageId]["_"], obj]; |
||||||
|
else |
||||||
|
info[notionPageId]["_"] = [obj]; |
||||||
|
} |
||||||
|
}); |
||||||
|
return info; |
||||||
|
} |
||||||
|
|
||||||
|
export function sleep(ms: number) { |
||||||
|
return new Promise(resolve => (setTimeout(resolve, ms))); |
||||||
|
} |
||||||
@ -0,0 +1,231 @@ |
|||||||
|
// import { Low, JSONFile } from 'lowdb';
|
||||||
|
import dotenv from 'dotenv'; |
||||||
|
dotenv.config(); |
||||||
|
|
||||||
|
import { Client as NotionClient } from '@notionhq/client'; |
||||||
|
import { Filter, RichText } from '@notionhq/client/build/src/api-types'; |
||||||
|
|
||||||
|
import { CreateText, GetAllTexts, UpdateCard, UpdateText } from './MiroHelper.js'; |
||||||
|
import { isEmptyOrSpaces, MiroSyncInfo, MiroWidgetInfo, ProcessMiroWidget, ProcessNotionBlock, ProcessNotionProperty, sleep } from './Utility.js'; |
||||||
|
|
||||||
|
// const adapter = new JSONFile<DBDataType>('db.json')
|
||||||
|
// const db = new Low<DBDataType>(adapter);
|
||||||
|
|
||||||
|
// Read data from JSON file, this will set db.data content
|
||||||
|
// (async () => await db.read())();
|
||||||
|
// db.data = db.data || { syncInfo: [] };
|
||||||
|
|
||||||
|
// (async () => await db.write())();//if db.json doesn't exist, write file will silent fail at program end
|
||||||
|
|
||||||
|
let miro_app_id = process.env.MIRO_APP_ID || ""; |
||||||
|
let notion_page = process.env.NOTION_PAGE || ""; |
||||||
|
let notion_token = process.env.NOTION_TOKEN || ""; |
||||||
|
let database_sync_page_count: Number = Number(process.env.DATABASE_SYNC_PAGE_COUNT) || 0; |
||||||
|
let synced_property_arr = (process.env.SYNCED_PROPERTY || "").split(','); |
||||||
|
let filter = (process.env.FILTER || ""); |
||||||
|
|
||||||
|
let verbose: Number = Number(process.env.VERBOSE) || 0; |
||||||
|
|
||||||
|
console.log(synced_property_arr); |
||||||
|
|
||||||
|
const notion = new NotionClient({ auth: notion_token }) |
||||||
|
|
||||||
|
const PrettyText = (textArr: RichText[]) => { |
||||||
|
let str = textArr.reduce((all, cur) => `${all}${cur.plain_text}`, ''); |
||||||
|
return `${str}`; |
||||||
|
} |
||||||
|
|
||||||
|
let counter = 0; |
||||||
|
let timestamp = 0; |
||||||
|
let remaining = 0; |
||||||
|
|
||||||
|
function MiroRateLimitCallback(rateLimitReset: number, rateLimitRemaining: number) { |
||||||
|
timestamp = rateLimitReset; |
||||||
|
remaining = rateLimitRemaining; |
||||||
|
} |
||||||
|
|
||||||
|
function GetTimer() { |
||||||
|
return Date.now() / 1000 - timestamp; |
||||||
|
} |
||||||
|
|
||||||
|
function ShouldIgnore(str: string) { |
||||||
|
return isEmptyOrSpaces(str) || str == "<p></p>"; |
||||||
|
} |
||||||
|
|
||||||
|
let x = 0; |
||||||
|
let y = 0; |
||||||
|
const COLUME_WIDTH = 200; |
||||||
|
const PART_OFFSET = 1000; |
||||||
|
|
||||||
|
function NewRow() { |
||||||
|
y += 120; |
||||||
|
} |
||||||
|
|
||||||
|
function ResetRow() { |
||||||
|
y = 0; |
||||||
|
} |
||||||
|
|
||||||
|
function NewColume() { |
||||||
|
x += COLUME_WIDTH; |
||||||
|
} |
||||||
|
|
||||||
|
function ResetColume() { |
||||||
|
x = 0; |
||||||
|
} |
||||||
|
|
||||||
|
async function SyncNotion2Miro(notionInfo: NotionInfoType, miroInfo?: MiroSyncInfo, onCreated?: () => void) { |
||||||
|
if (ShouldIgnore(notionInfo.text)) { |
||||||
|
console.log(`\tskipping empty string block\n`); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
counter++; |
||||||
|
|
||||||
|
await sleep(100); |
||||||
|
if (counter % 10 == 0) { |
||||||
|
console.log('sleep to prevent miro blocking'); |
||||||
|
await sleep(2000); |
||||||
|
} |
||||||
|
|
||||||
|
while (remaining <= 1000 && GetTimer() < 0) { |
||||||
|
console.log(`sleep to prevent miro blocking, timer[${GetTimer().toFixed()}]`); |
||||||
|
await sleep(1000); |
||||||
|
} |
||||||
|
|
||||||
|
console.log(`miro sync counter[${counter}] timer[${GetTimer().toFixed()}] remaining points[${remaining}]`); |
||||||
|
|
||||||
|
if (miroInfo == undefined) return; |
||||||
|
|
||||||
|
let notionPageId = notionInfo.pageId; |
||||||
|
let notionPropertyId = notionInfo.propertyId; |
||||||
|
|
||||||
|
let info = undefined; |
||||||
|
if (notionPropertyId === undefined) |
||||||
|
info = miroInfo[notionPageId] && miroInfo[notionPageId]["_"]; |
||||||
|
else |
||||||
|
info = miroInfo[notionPageId] && miroInfo[notionPageId][notionPropertyId]; |
||||||
|
|
||||||
|
async function CreateOrUpdateInfo(info?: MiroWidgetInfo) { |
||||||
|
if (info === undefined) { |
||||||
|
console.log(`\tcreate text: ${notionInfo.text}\n`); |
||||||
|
// CreateCard(notionInfo.text, { notionId: notionInfo.id });
|
||||||
|
await CreateText(notionInfo.text, { notionPageId, notionPropertyId }, x, y, MiroRateLimitCallback); |
||||||
|
// db.data?.syncInfo.push({ notionId: id });
|
||||||
|
} |
||||||
|
else { |
||||||
|
switch (info.type) {//conversion?
|
||||||
|
case 'card': |
||||||
|
console.log(`\tupdate miro card[${info.id}] ${notionInfo.text}\n`); |
||||||
|
await UpdateCard(info.id, notionInfo.text, undefined, MiroRateLimitCallback); |
||||||
|
break; |
||||||
|
case 'text': |
||||||
|
console.log(`\tupdate miro text[${info.id}] ${notionInfo.text}\n`); |
||||||
|
await UpdateText(info.id, notionInfo.text, undefined, MiroRateLimitCallback); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (let index = 0; index < 2; index++) { |
||||||
|
if (info === undefined) { |
||||||
|
await CreateOrUpdateInfo(); |
||||||
|
onCreated && onCreated(); |
||||||
|
if (index == 0) x += PART_OFFSET; |
||||||
|
else x -= PART_OFFSET + COLUME_WIDTH; |
||||||
|
} |
||||||
|
else |
||||||
|
await CreateOrUpdateInfo(info[index]); |
||||||
|
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// async function SyncNotionPage(pageId: string, miroWidgetInfo: MiroSyncInfo) {
|
||||||
|
// let blocks = await notion.blocks.children.list({ block_id: pageId });
|
||||||
|
|
||||||
|
// console.log(blocks);
|
||||||
|
// //TODO deal with pagination
|
||||||
|
// if (blocks == undefined) return;
|
||||||
|
// if (miroWidgetInfo == undefined) return;
|
||||||
|
|
||||||
|
// if (blocks.has_more)
|
||||||
|
// console.warn("Need to deal with paging results!!");
|
||||||
|
// if (verbose)
|
||||||
|
// console.log(JSON.stringify(blocks.results, null, "\t"));
|
||||||
|
|
||||||
|
// let processedBlocks = ProcessNotionBlock(blocks.results);
|
||||||
|
// for (var block of processedBlocks) {
|
||||||
|
// if (block == null) continue;
|
||||||
|
// if (block.richText == null) {
|
||||||
|
// if (block.id == null) continue;
|
||||||
|
// console.warn(`try to handle unsupported block[${block.id}]`);
|
||||||
|
// await SyncNotionDatabase(block.id, miroWidgetInfo);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// let text = PrettyText(block.richText);
|
||||||
|
// console.log(`block[${block.id}] notionInfo.text`);
|
||||||
|
// await SyncNotion2Miro({ pageId: block.id, text: `<p>${text}</p>` }, miroWidgetInfo);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
async function SyncNotionDatabase(databaseId: string, miroWidgetInfo?: MiroSyncInfo) { |
||||||
|
try { |
||||||
|
let db = await notion.databases.retrieve({ database_id: databaseId }); |
||||||
|
console.log(`Processing Database: ${PrettyText(db.title)}`); |
||||||
|
let _filter = JSON.parse(filter) as Filter; |
||||||
|
console.log(filter); |
||||||
|
let pages = await notion.databases.query({ database_id: databaseId, filter: _filter }); |
||||||
|
|
||||||
|
//TODO deal with pagination
|
||||||
|
if (pages == undefined) return; |
||||||
|
if (miroWidgetInfo == undefined) return; |
||||||
|
|
||||||
|
let count = 0; |
||||||
|
for (let page of pages.results) { |
||||||
|
let processedProperties = ProcessNotionProperty(Object.entries(page.properties)); |
||||||
|
for (let property of processedProperties) { |
||||||
|
if (property == null) continue; |
||||||
|
if (synced_property_arr.includes(property.name) == false) continue; |
||||||
|
|
||||||
|
if (database_sync_page_count != 0 && count >= database_sync_page_count) continue; |
||||||
|
count++; |
||||||
|
|
||||||
|
let text = property.richText ? PrettyText(property.richText) : property.text; |
||||||
|
text = text || ""; |
||||||
|
|
||||||
|
console.log(`property[${property.id}] ${text}`); |
||||||
|
let notionInfo = { pageId: page.id, propertyId: property.id, text: `<p>${text}</p>` }; |
||||||
|
|
||||||
|
if (ShouldIgnore(text)) NewColume(); |
||||||
|
|
||||||
|
await SyncNotion2Miro(notionInfo, miroWidgetInfo, NewColume); |
||||||
|
} |
||||||
|
ResetColume(); |
||||||
|
NewRow(); |
||||||
|
} |
||||||
|
} catch (err) { |
||||||
|
} finally { |
||||||
|
return []; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(async () => { |
||||||
|
try { |
||||||
|
let collection = await GetAllTexts(); |
||||||
|
let info = ProcessMiroWidget(collection.data, miro_app_id); |
||||||
|
|
||||||
|
// console.log(JSON.stringify(collection));
|
||||||
|
await SyncNotionDatabase(notion_page, info); |
||||||
|
// await SyncNotionPage(notion_page, info);
|
||||||
|
|
||||||
|
// await db.write();
|
||||||
|
} catch (error) { |
||||||
|
console.log(error); |
||||||
|
} |
||||||
|
})(); |
||||||
|
|
||||||
|
type NotionInfoType = { |
||||||
|
pageId: string, |
||||||
|
propertyId?: string, |
||||||
|
text: string |
||||||
|
} |
||||||
@ -0,0 +1,38 @@ |
|||||||
|
{ |
||||||
|
"name": "node-notion2miro", |
||||||
|
"version": "3.0.0", |
||||||
|
"main": "index.js", |
||||||
|
"license": "MIT", |
||||||
|
"bin": "./dist/index.js", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "yarn dev:compile && yarn dev:run", |
||||||
|
"dev:compile": "tsc", |
||||||
|
"dev:run": "node dist/index.js", |
||||||
|
"build": "./node_modules/.bin/esbuild index.ts --bundle --outfile=./dist/index.js --platform=node && pkg ." |
||||||
|
}, |
||||||
|
"pkg": { |
||||||
|
"targets": [ |
||||||
|
"node14-win-x64" |
||||||
|
], |
||||||
|
"options": [ |
||||||
|
"experimental-modules" |
||||||
|
], |
||||||
|
"outputPath": "exe" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/node": "^15.12.1", |
||||||
|
"@vercel/ncc": "^0.28.6", |
||||||
|
"concurrently": "^6.0.2", |
||||||
|
"nodemon": "^2.0.7", |
||||||
|
"pkg": "^5.2.1", |
||||||
|
"typescript": "^4.2.4" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@notionhq/client": "^0.2.1", |
||||||
|
"dotenv": "^9.0.2", |
||||||
|
"esbuild": "^0.12.6", |
||||||
|
"got": "^11.8.2", |
||||||
|
"lowdb": "^2.1.0" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,74 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */ |
||||||
|
/* Basic Options */ |
||||||
|
// "incremental": true, /* Enable incremental compilation */ |
||||||
|
"target": "ESNext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ |
||||||
|
"module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ |
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */ |
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */ |
||||||
|
// "checkJs": true, /* Report errors in .js files. */ |
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ |
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */ |
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ |
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */ |
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */ |
||||||
|
"outDir": "./dist", /* Redirect output structure to the directory. */ |
||||||
|
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ |
||||||
|
// "composite": true, /* Enable project compilation */ |
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ |
||||||
|
// "removeComments": true, /* Do not emit comments to output. */ |
||||||
|
// "noEmit": true, /* Do not emit outputs. */ |
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */ |
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ |
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ |
||||||
|
/* Strict Type-Checking Options */ |
||||||
|
"strict": true, /* Enable all strict type-checking options. */ |
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ |
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */ |
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ |
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ |
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ |
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ |
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ |
||||||
|
/* Additional Checks */ |
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */ |
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */ |
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ |
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ |
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ |
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ |
||||||
|
/* Module Resolution Options */ |
||||||
|
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ |
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ |
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ |
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ |
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */ |
||||||
|
"types": [ |
||||||
|
"node" |
||||||
|
], /* Type declaration files to be included in compilation. */ |
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ |
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ |
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ |
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ |
||||||
|
/* Source Map Options */ |
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ |
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ |
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ |
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ |
||||||
|
/* Experimental Options */ |
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ |
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ |
||||||
|
/* Advanced Options */ |
||||||
|
"skipLibCheck": true, /* Skip type checking of declaration files. */ |
||||||
|
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ |
||||||
|
}, |
||||||
|
"exclude": [ |
||||||
|
"dist", |
||||||
|
"node_modules", |
||||||
|
"db.json" |
||||||
|
], |
||||||
|
"include": [ |
||||||
|
"./" |
||||||
|
] |
||||||
|
} |
||||||
Loading…
Reference in new issue