Skip to content

Instantly share code, notes, and snippets.

@darwis059
Created May 16, 2023 00:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save darwis059/7ddfad95d702fa731272ce65291e4595 to your computer and use it in GitHub Desktop.
Save darwis059/7ddfad95d702fa731272ce65291e4595 to your computer and use it in GitHub Desktop.
import {
Observable,
Subscription,
debounceTime,
filter,
mergeAll,
} from 'rxjs';
import { getParentUuid } from './utils';
import { blockUpdated, currentPage, pageUpdateObs } from './subject';
import {
observeOnMutation,
pipeFinishEdit,
pipeStartEdit,
pipePageTagged,
pipeDoneTask,
} from './rxjsPipe';
const targetNode: HTMLElement = <HTMLElement>(
document.getElementById('app-container')
);
const config = { attributes: false, childList: true, subtree: true };
let observeFinishEditBlock: Subscription;
let observeEditingBlock: Subscription;
let observeStartEditBlock: Observable<string[]>;
let observePageUpdate: Subscription;
let observePageTagged: Subscription;
let observeDoneTask: Subscription;
const startMutationObservable = () => {
observePageUpdate = pageUpdateObs.subscribe(console.log);
const mutationObs = observeOnMutation(targetNode, config).pipe(
mergeAll()
// tap(console.log)
);
observeStartEditBlock = pipeStartEdit(mutationObs);
observeFinishEditBlock = pipeFinishEdit(
mutationObs,
observeStartEditBlock
).subscribe(([finishEdit, startEdit]) => {
// console.log({ finishEdit, startEdit });
if (
!(
finishEdit.content === startEdit[0] ||
finishEdit.content === startEdit[1]
)
) {
blockUpdated.next({
type: 'finished',
uuids: finishEdit.parentUuid,
page: encodeURIComponent(location.hash).split('/page/')?.[1],
});
// backward compability
dispatchEvent(
new CustomEvent('blockupdated', {
detail: { uuid: finishEdit.parentUuid },
})
);
}
});
// export the observeFinishEditBlock before subscribed. import on needed component to subscribe it
// or create subject for edited block, call subject.next in observeFinishEditBlock subscribe function.
// able to trigger block updated event from anywhere not only from mutation observer.
observeEditingBlock = mutationObs
.pipe(
filter((m) => (<HTMLElement>m.target).id === 'mock-text'),
debounceTime(300)
)
.subscribe((m) => {
const targetEl = <HTMLElement>m.target;
const uuid = targetEl.closest('div[blockid]')?.getAttribute('blockid');
const parentUuid = getParentUuid(targetEl);
// console.log({ uuid, parentUuid });
blockUpdated.next({
type: 'editing',
uuids: parentUuid,
page: encodeURIComponent(location.hash).split('/page/')?.[1],
});
// dispatchEvent(
// new CustomEvent('newblockupdated', {
// detail: { type: 'editing', uuids: parentUuid },
// })
// );
});
observePageTagged = pipePageTagged(mutationObs).subscribe((tagsDom) => {
const pageNames = tagsDom.map((t) => ({
el: t,
tag: decodeURIComponent(t.querySelector('a').getAttribute('href')).split(
'/page/'
)[1],
}));
const relatedPage = logseq.api
.datascript_query(
`[:find (pull ?b [:db/id :block/uuid :block/name {:block/tags [:block/name :db/id]}])
:in $ ?names
:where
[?b :block/name ?name]
[(contains? ?names ?name)]
]`,
`#{${pageNames.map((s) => `"${s.tag}"`).join(' ')}}`
)
.flat() as LogseqPage[];
pageNames.forEach((pageName) => {
const page = relatedPage.filter((p) => p.name === pageName.tag)[0];
if (page.tags) {
const spanDom = document.createElement('span');
spanDom.classList.add('ml-4');
page.tags
.filter((t) => t.name !== currentPage)
.forEach((tag) => {
const tagDom = document.createElement('a');
tagDom.setAttribute(
'href',
`#/page/${encodeURIComponent(tag.name)}`
);
tagDom.textContent = `#${tag.name}`;
tagDom.classList.add('px-1', 'text-green-300');
spanDom.appendChild(tagDom);
});
pageName.el.appendChild(spanDom);
}
});
});
observeDoneTask = pipeDoneTask(mutationObs).subscribe((uuid) => {
console.log(uuid);
const today = new Date();
logseq.api.upsert_block_property(
uuid,
'done',
`${today.getFullYear()}${(today.getMonth() + 1)
.toString()
.padStart(2, '0')}${today.getDate()}`
);
});
};
export { startMutationObservable };
if (import.meta.hot) {
import.meta.hot.accept((obs) => {
// import.meta.hot.invalidate();
if (obs) {
observeFinishEditBlock.unsubscribe();
observeEditingBlock.unsubscribe();
observePageUpdate.unsubscribe();
observePageTagged.unsubscribe();
observeDoneTask.unsubscribe();
obs?.startMutationObservable();
console.log('updated observer module', obs);
}
});
}
export const checkNodes = (
node: NodeList,
checkFn: (el: HTMLElement) => Boolean
) =>
Array.from(node)
.filter((n) => n.nodeType !== Node.TEXT_NODE)
.some(
(n) => {
// const nEl = n as HTMLElement;
const nEl = 'querySelector' in n ? <HTMLElement>n : n.parentElement;
return checkFn(nEl); //nEl.classList.contains(className);
} //n.parentElement?.classList.contains('editor-wrapper')
);
export const pipeDoneTask = (obs: Observable<MutationRecord>) =>
obs.pipe(
filter(
(m) =>
(<HTMLElement>m.target)?.tagName === 'SPAN' &&
(<HTMLElement>m.target).classList.contains('done')
),
throttleTime(200),
map((m) => getUuid(m.target.parentElement))
);
export const pipePageTagged = (obs: Observable<MutationRecord>) =>
obs.pipe(
filter(
(m) =>
m.addedNodes.length > 0 &&
checkNodes(
m.addedNodes,
(el) => !!el.querySelector('div.references.page-tags')
)
),
map((m) =>
Array.from(
(m.addedNodes[0] as HTMLElement)
.querySelector('div.references.page-tags')
.querySelectorAll('li')
)
)
// mergeAll()
);
export const pipeStartEdit = (obs: Observable<MutationRecord>) =>
obs.pipe(
filter(
(m) =>
m.addedNodes.length > 0 &&
checkNodes(m.addedNodes, (el) => el.classList.contains('editor-inner'))
// Array.from(m.addedNodes)
// .filter((n) => n.nodeType !== Node.TEXT_NODE)
// .some(
// (n) => {
// const nEl = n as HTMLElement;
// return nEl.classList.contains('editor-inner');
// } //n.parentElement?.classList.contains('editor-wrapper')
// )
),
map((m) =>
(<HTMLElement>m.target).querySelector('textarea').textContent.trim()
),
pairwise()
// tap(console.log)
);
export const pipeFinishEdit = (
obs: Observable<MutationRecord>,
observeStartEditBlock: Observable<any>
) =>
obs.pipe(
filter(
(m) =>
m.removedNodes.length > 0 &&
checkNodes(m.removedNodes, (el) =>
el.classList.contains('editor-inner')
)
// Array.from(m.removedNodes)
// .filter((n) => n.nodeType !== Node.TEXT_NODE)
// .some(
// (n) => {
// const nEl = n as HTMLElement;
// return nEl.classList.contains('editor-inner');
// } //n.parentElement?.classList.contains('editor-wrapper')
// )
),
map((m) => {
const targetEl = <HTMLElement>m.target;
const uuid = targetEl.closest('div[blockid]')?.getAttribute('blockid');
const logseqContent = logseq.api.get_block(uuid).content;
return {
uuid,
parentUuid: getParentUuid(targetEl),
content: logseqContent || targetEl.textContent.trim(),
};
}),
withLatestFrom(observeStartEditBlock)
);
export const observeOnMutation = (
target,
config
): Observable<MutationRecord[]> => {
return new Observable((observer) => {
const mutation = new MutationObserver((mutations, instance) => {
pageUpdated.next(decodeURIComponent(location.hash));
observer.next(mutations);
});
mutation.observe(target, config);
const unsubscribe = () => {
mutation.disconnect();
};
return unsubscribe;
});
};
if (import.meta.hot) {
import.meta.hot.accept((obs) => {
// import.meta.hot.invalidate();
console.log('updated util module', obs);
});
}
import { Subject, filter, pairwise, tap } from 'rxjs';
let currentPage = '';
const blockUpdated = new Subject<{
type: 'finished' | 'editing';
uuids: string[];
page: string;
}>();
const pageUpdated = new Subject<string>(); //.pipe(pairwise(),tap(console.log));
pageUpdated.next('');
const pageUpdateObs = pageUpdated.pipe(
pairwise(),
tap((p) => (currentPage = p[1].split('/page/')[1])),
filter((p) => p[0] !== p[1])
);
export { blockUpdated, pageUpdated, pageUpdateObs, currentPage };
export function getParentUuid(el: HTMLElement, uuid: string[] = []): string[] {
const blockEl = el.closest('div[blockid]') as HTMLElement;
// console.log({ blockEl });
if (blockEl) {
uuid.push(blockEl.getAttribute('blockid'));
return getParentUuid(blockEl.parentElement, uuid);
}
return uuid;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment