Skip to content

Commit

Permalink
feat: add fetch toc mode
Browse files Browse the repository at this point in the history
api updates; popup menu on toggle addon menu; add mode to tap on note to fetch children recursively

BREAKING CHANGE: api: add ReturnBody_Toc; mediaList -> mediaMap; add bookMap for ReturnBody_Note and
ReturnBody_Toc ...
  • Loading branch information
aidenlx committed Aug 22, 2021
1 parent c7e242d commit c9bf479
Show file tree
Hide file tree
Showing 12 changed files with 470 additions and 195 deletions.
2 changes: 1 addition & 1 deletion mnaddon.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"author": "AidenLx",
"title": "Obsidian Bridge",
"version": "1.1.2",
"marginnote_version_min": "3.7.5",
"marginnote_version_min": "3.7.12",
"cert_key": ""
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"author": "AidenLx",
"license": "MIT",
"devDependencies": {
"@alx-plugins/marginnote": "^1.7.4",
"@alx-plugins/marginnote": "^1.8.3",
"@release-it/bumper": "^3.0.1",
"@release-it/conventional-changelog": "^3.3.0",
"@rollup/plugin-commonjs": "^20.0.0",
Expand Down
33 changes: 14 additions & 19 deletions src/eventHandlers.ts → src/event-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {
ProcessNewExcerpt_Sender,
} from "@alx-plugins/marginnote";
import PopupRecorder from "modules/PopupRecorder";
import { copy, showHUD } from "modules/tools";
import { showHUD } from "modules/tools";
import { addonOnName } from "togglePlugin";

import { stringify } from "./modules/parser";
import { handleNote, handleSel, handleToc } from "./modules/parser";

export const onPopupMenuOnNote = (sender: PopupMenuOnNote_Sender) => {
if (
Expand All @@ -25,21 +25,17 @@ export const onPopupMenuOnNote = (sender: PopupMenuOnNote_Sender) => {
self.recorder = new PopupRecorder();
}

if (self.recorder.isDuplicate(Date.now())) return;

const srcNote = sender.userInfo.note;
let currentBook;

if (!srcNote) {
showHUD("no note in sender");
if (!sender.userInfo.note) {
showHUD("Error: No note in sender");
return;
}

if (srcNote.docMd5)
currentBook = Database.sharedInstance().getDocumentById(srcNote.docMd5);

try {
copy(stringify(srcNote, self.recorder, currentBook));
if (self.recorder.isDuplicate(Date.now())) return;

const note = sender.userInfo.note;

self.tocMode ? handleToc(note) : handleNote(note);
} catch (error) {
showHUD(error.toString());
}
Expand All @@ -51,18 +47,17 @@ export const onPopupMenuOnSelection = (sender: PopupMenuOnSelection_Sender) => {
)
return; //Don't process message from other window

if (!self[addonOnName]) return;
if (!self[addonOnName] || self.tocMode) return;

if (self.recorder === undefined) {
self.recorder = new PopupRecorder();
}

const { selectionText: selection, document: currentBook } = sender.userInfo
.documentController as DocumentController;

try {
if (selection && selection.length) {
copy(stringify({ sel: selection }, self.recorder, currentBook));
const { selectionText: sel, document: book } =
sender.userInfo.documentController;
if (sel && sel.length) {
handleSel({ sel, book });
}
} catch (error) {
showHUD(error.toString());
Expand Down
11 changes: 9 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ClsMembers, InstMembers } from "@alx-plugins/marginnote";
import { getObjCClassDeclar as getDeclar } from "modules/tools";
import { getObjCClassDeclar as getDeclar, showHUD } from "modules/tools";
import {
addonOnName,
pluginName,
Expand All @@ -11,7 +11,9 @@ import {
bindEventHandlers,
onPopupMenuOnNote,
onPopupMenuOnSelection,
} from "./eventHandlers";
} from "./event-handlers";
import PopupRecorder from "./modules/PopupRecorder";
import getText from "./modules/translate";

const bindEvt = bindEventHandlers([
{ event: "PopupMenuOnSelection", handler: onPopupMenuOnSelection },
Expand All @@ -20,11 +22,16 @@ const bindEvt = bindEventHandlers([

const inst: InstMembers = {
...bindEvt.handlers,
sceneWillConnect: () => {
self.tocMode = false;
self.recorder = new PopupRecorder();
},
notebookWillOpen: (notebookid) => {
bindEvt.add();
self[addonOnName] = NSUserDefaults.standardUserDefaults().objectForKey(
`marginnote_${pluginName}`,
);
if (self[addonOnName] && self.tocMode) showHUD(getText("warn_toc_enabled"));
},
notebookWillClose: (notebookid) => {
bindEvt.remove();
Expand Down
9 changes: 7 additions & 2 deletions src/modules/PopupRecorder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ export default class PopupRecorder {
this.addTime = new Array(null, null);
}

push(obj: Exclude<inHistory, null>) {
/**
* @returns add time
*/
push(obj: Exclude<inHistory, null>): number {
this.history.shift(); //去除过期id(第一个)
this.history.push(obj);
this.addTime.shift(); //去除过期id(第一个)
this.addTime.push(Date.now());
const addTime = Date.now();
this.addTime.push(addTime);
return addTime;
}

public get last(): item {
Expand Down
15 changes: 15 additions & 0 deletions src/modules/optional.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
type Undefined<T> = { [P in keyof T]: P extends undefined ? T[P] : never };

type FilterFlags<Base, Condition> = {
[Key in keyof Base]: Base[Key] extends Condition ? Key : never;
};

type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];

type SubType<Base, Condition> = Pick<Base, AllowedNames<Base, Condition>>;

export type OptionalKeys<T> = Exclude<
keyof T,
NonNullable<keyof SubType<Undefined<T>, never>>
>;
export type RequiredKeys<T> = NonNullable<keyof SubType<Undefined<T>, never>>;
150 changes: 102 additions & 48 deletions src/modules/parser.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,110 @@
import { excerptPic_video, MbBook } from "@alx-plugins/marginnote";
import { excerptPic_video, MbBook, MbBookNote } from "@alx-plugins/marginnote";

import PopupRecorder from "./PopupRecorder";
import { item, MNMark, node, ReturnBody, selection } from "./return";
import { scanObject } from "./tools";

const process = (node: node, rec: PopupRecorder, book?: MbBook): ReturnBody => {
const currentBook = book ? scanObject(book) : undefined;

const getLastAndSendTime = (
data: node,
): { sendTime: ReturnBody["sendTime"]; last: ReturnBody["last"] } => {
const last = rec.last;
rec.push(data);
const sendTime = (rec.last as Exclude<item, null>).addTime;
return { last, sendTime };
};
import {
Data,
MNMark,
ReturnBody,
ReturnBody_Note,
ReturnBody_Sel,
ReturnBody_Toc,
Selection,
} from "./return";
import { scanNote, scanObject, scanToc } from "./scan";
import { copy, showHUD } from "./tools";
import getText from "./translate";

if (isSel(node)) {
const data = node;
const { last, sendTime } = getLastAndSendTime(data);
const mediaList = null;
return { type: "sel", sendTime, currentBook, mediaList, data, last };
} else {
const data = scanObject(node, 2);
const { last, sendTime } = getLastAndSendTime(data);
const videoId = (data.excerptPic as excerptPic_video)?.video;
const mediaList = [];
const mediaIds = node.mediaList?.split("-").filter((id) => id !== videoId);
if (mediaIds && mediaIds.length > 1) {
for (const id of mediaIds) {
if (!id) continue; // escape empty string
const mediaData = Database.sharedInstance()
.getMediaByHash(id)
?.base64Encoding();
// only export png, cannot find way to process stroke properly for now
if (mediaData && mediaData.startsWith("iVBORw0K"))
mediaList.push({ id, data: mediaData });
}
}
return { type: "note", sendTime, currentBook, mediaList, data, last };
}
const getBook = (docMd5: string | undefined): MbBook | null => {
const bookObj =
docMd5 && typeof docMd5 === "string"
? Database.sharedInstance().getDocumentById(docMd5)
: null;
return bookObj ? scanObject(bookObj) : null;
};

const MNMark: MNMark = "<!--MN-->\n";
const stringify = (obj: any): string => {
return MNMark + JSON.stringify(obj);
};

const getLastAndSendTime = (
data: Data,
): { sendTime: ReturnBody["sendTime"]; last: ReturnBody["last"] } => {
const rec = self.recorder as PopupRecorder,
last = rec.last;
return { last, sendTime: rec.push(data) };
};

export const handleSel = (sel: Selection): void => {
const { last, sendTime } = getLastAndSendTime(sel);
const returns: ReturnBody_Sel = {
type: "sel",
sendTime,
data: sel,
last,
};
copy(stringify(returns));
};

export const stringify = <T extends node>(
node: T,
rec: PopupRecorder,
currentBook?: MbBook,
): string => {
return MNMark + JSON.stringify(process(node, rec, currentBook));
const arrToObj = <V>(
arr: string[],
cvt: (id: string) => V | null,
): Record<string, V> =>
arr.reduce((obj, id) => {
const val = cvt(id);
if (val) obj[id] = val;
return obj;
}, {} as any) as Record<string, V>;

export const handleNote = (note: MbBookNote): void => {
const [data, bookMd5s] = scanNote(note, 2),
{ last, sendTime } = getLastAndSendTime(data),
bookMap = arrToObj(bookMd5s, (id) => getBook(id));

const videoId = (data.excerptPic as excerptPic_video)?.video,
mediaIds = note.mediaList?.split("-").filter((id) => id !== videoId),
mediaMap =
mediaIds && mediaIds.length > 0
? arrToObj(mediaIds, (id) => {
const mediaData = Database.sharedInstance()
.getMediaByHash(id)
?.base64Encoding();
return mediaData && mediaData.startsWith("iVBORw0K")
? mediaData
: null;
})
: {};

const returns: ReturnBody_Note = {
type: "note",
sendTime,
bookMap,
mediaMap,
data,
last,
};
copy(stringify(returns));
};

const isSel = (node: node): node is selection =>
typeof (node as selection).sel === "string";
export const handleToc = (note: MbBookNote): void => {
// if (note.parentNote) return;
const result = scanToc(note);
if (typeof result !== "string") {
const [data, bookMd5s] = result,
{ last, sendTime } = getLastAndSendTime(data),
bookMap = arrToObj(bookMd5s, (id) => getBook(id));
const returns: ReturnBody_Toc = {
type: "toc",
sendTime,
bookMap,
data,
last,
};
copy(stringify(returns));
showHUD(getText("hint_toc_success") + note.noteTitle);
} else showHUD(result);
};

const MNMark: MNMark = "<!--MN-->\n";

const isSel = (node: Data): node is Selection =>
typeof (node as Selection).sel === "string";
42 changes: 26 additions & 16 deletions src/modules/return.d.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,52 @@
import { MbBook, MbBookNote } from "@alx-plugins/marginnote";

export type selection = { sel: string };
export type inHistory = selection | MbBookNote | null;
export type Selection = { sel: string; book?: MbBook };
export type inHistory = Data | null;
export type time = number | null;
export type item = {
data: Exclude<inHistory, null>;
addTime: Exclude<time, null>;
} | null;

export type node = selection | MbBookNote;
export type Data = Selection | MbBookNote | Toc;
export type DataType = "sel" | "note" | "toc";

export type MNMark = "<!--MN-->\n";

type Media = {
id: string;
/** encoded in base64 */
data: string;
};

type ReturnBody_Basic = {
type: "sel" | "note";
type: DataType;
sendTime: ReturnType<typeof Date.now>;
currentBook?: MbBook;
mediaList: Media[] | null;
data: node;
data: Data;
last: item | null;
};

export type ReturnBody = ReturnBody_Note | ReturnBody_Sel;

export interface ReturnBody_Sel extends ReturnBody_Basic {
type: "sel";
data: selection;
mediaList: null;
data: Selection;
book?: MbBook;
}

export interface ReturnBody_Note extends ReturnBody_Basic {
type: "note";
data: MbBookNote;
mediaList: Array<{ id: string; data: string }>;
/** id - base64(png) pair */
mediaMap: Record<string, string>;
bookMap: Record<string, MbBook>;
}

export interface ReturnBody_Toc extends ReturnBody_Basic {
type: "toc";
data: Toc;
bookMap: Record<string, MbBook>;
}
export interface Toc {
noteTitle: string;
noteId: string;
docMd5?: string;
startPage?: number;
endPage?: number;

childNotes: Toc[];
}

0 comments on commit c9bf479

Please sign in to comment.