Yay lazy loading
At long last.
This commit is contained in:
@@ -28,6 +28,7 @@
|
||||
import { cancelable, CancelablePromise } from 'cancelable-promise'
|
||||
import { debounce } from 'lodash-es'
|
||||
import type OmnisearchPlugin from '../main'
|
||||
import LazyLoader from './lazy-loader/LazyLoader.svelte'
|
||||
|
||||
export let modal: OmnisearchVaultModal
|
||||
export let previousQuery: string | undefined
|
||||
@@ -111,9 +112,7 @@
|
||||
|
||||
async function prevSearchHistory() {
|
||||
// Filter out the empty string, if it's there
|
||||
const history = (await plugin.searchHistory.getHistory()).filter(
|
||||
s => s
|
||||
)
|
||||
const history = (await plugin.searchHistory.getHistory()).filter(s => s)
|
||||
if (++historySearchIndex >= history.length) {
|
||||
historySearchIndex = 0
|
||||
}
|
||||
@@ -122,9 +121,7 @@
|
||||
}
|
||||
|
||||
async function nextSearchHistory() {
|
||||
const history = (await plugin.searchHistory.getHistory()).filter(
|
||||
s => s
|
||||
)
|
||||
const history = (await plugin.searchHistory.getHistory()).filter(s => s)
|
||||
if (--historySearchIndex < 0) {
|
||||
historySearchIndex = history.length ? history.length - 1 : 0
|
||||
}
|
||||
@@ -240,10 +237,18 @@
|
||||
// Generate link
|
||||
let link: string
|
||||
if (file && active) {
|
||||
link = plugin.app.fileManager.generateMarkdownLink(file, active.path, "", selectedNote.displayTitle)
|
||||
link = plugin.app.fileManager.generateMarkdownLink(
|
||||
file,
|
||||
active.path,
|
||||
'',
|
||||
selectedNote.displayTitle
|
||||
)
|
||||
} else {
|
||||
const maybeDisplayTitle = selectedNote.displayTitle === "" ? "" : `|${selectedNote.displayTitle}`
|
||||
link = `[[${selectedNote.basename}.${getExtension(selectedNote.path)}${maybeDisplayTitle}]]`
|
||||
const maybeDisplayTitle =
|
||||
selectedNote.displayTitle === '' ? '' : `|${selectedNote.displayTitle}`
|
||||
link = `[[${selectedNote.basename}.${getExtension(
|
||||
selectedNote.path
|
||||
)}${maybeDisplayTitle}]]`
|
||||
}
|
||||
|
||||
// Inject link
|
||||
@@ -255,7 +260,7 @@
|
||||
modal.close()
|
||||
}
|
||||
|
||||
function switchToInFileModal(): void {
|
||||
function switchToInFileModal(): void {
|
||||
// Do nothing if the selectedNote is a PDF,
|
||||
// or if there is 0 match (e.g indexing in progress)
|
||||
if (
|
||||
@@ -323,15 +328,21 @@
|
||||
|
||||
<ModalContainer>
|
||||
{#each resultNotes as result, i}
|
||||
<ResultItemVault
|
||||
{plugin}
|
||||
selected="{i === selectedIndex}"
|
||||
note="{result}"
|
||||
on:mousemove="{_ => (selectedIndex = i)}"
|
||||
on:click="{onClick}"
|
||||
on:auxclick="{evt => {
|
||||
if (evt.button == 1) openNoteInNewPane()
|
||||
}}" />
|
||||
<LazyLoader
|
||||
height="{100}"
|
||||
offset="{500}"
|
||||
keep="{true}"
|
||||
fadeOption="{{ delay: 0, duration: 0 }}">
|
||||
<ResultItemVault
|
||||
plugin="{plugin}"
|
||||
selected="{i === selectedIndex}"
|
||||
note="{result}"
|
||||
on:mousemove="{_ => (selectedIndex = i)}"
|
||||
on:click="{onClick}"
|
||||
on:auxclick="{evt => {
|
||||
if (evt.button == 1) openNoteInNewPane()
|
||||
}}" />
|
||||
</LazyLoader>
|
||||
{/each}
|
||||
<div style="text-align: center;">
|
||||
{#if !resultNotes.length && searchQuery && !searching}
|
||||
|
||||
208
src/components/lazy-loader/LazyLoader.svelte
Normal file
208
src/components/lazy-loader/LazyLoader.svelte
Normal file
@@ -0,0 +1,208 @@
|
||||
<div use:load class={rootClass} style="height: {rootInitialHeight}">
|
||||
{#if loaded}
|
||||
<div
|
||||
in:fade={fadeOption || {}}
|
||||
class={contentClass}
|
||||
style={contentStyle}
|
||||
>
|
||||
<slot>Lazy load content</slot>
|
||||
</div>
|
||||
{#if !contentShow && placeholder}
|
||||
<Placeholder {placeholder} {placeholderProps} />
|
||||
{/if}
|
||||
{:else if placeholder}
|
||||
<Placeholder {placeholder} {placeholderProps} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// https://github.com/leafOfTree/svelte-lazy
|
||||
import { fade } from 'svelte/transition';
|
||||
import Placeholder from './Placeholder.svelte';
|
||||
export let keep = false;
|
||||
export let height = 0;
|
||||
export let offset = 150;
|
||||
export let fadeOption = {
|
||||
delay: 0,
|
||||
duration: 400,
|
||||
};
|
||||
export let resetHeightDelay = 0;
|
||||
export let onload = null;
|
||||
export let placeholder = null;
|
||||
export let placeholderProps = null;
|
||||
let className = '';
|
||||
export { className as class };
|
||||
|
||||
const rootClass = 'svelte-lazy'
|
||||
+ (className ? ' ' + className : '');
|
||||
const contentClass = 'svelte-lazy-content';
|
||||
const rootInitialHeight = getStyleHeight();
|
||||
let loaded = false;
|
||||
|
||||
let contentShow = true;
|
||||
$: contentStyle = !contentShow ? 'display: none' : '';
|
||||
|
||||
function load(node) {
|
||||
setHeight(node);
|
||||
const handler = createHandler(node);
|
||||
addListeners(handler);
|
||||
setTimeout(() => {
|
||||
handler();
|
||||
});
|
||||
const observer = observeNode(node, handler);
|
||||
|
||||
return {
|
||||
destroy: () => {
|
||||
removeListeners(handler);
|
||||
observer.unobserve(node);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createHandler(node) {
|
||||
const handler = throttle(e => {
|
||||
const nodeTop = node.getBoundingClientRect().top;
|
||||
const nodeBottom = node.getBoundingClientRect().bottom;
|
||||
const expectedTop = getContainerHeight(e) + offset;
|
||||
|
||||
if (nodeTop <= expectedTop && nodeBottom > 0) {
|
||||
loadNode(node);
|
||||
} else if (!keep) {
|
||||
unload(node)
|
||||
}
|
||||
}, 200);
|
||||
return handler;
|
||||
}
|
||||
|
||||
function observeNode(node, handler) {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
loadNode(node);
|
||||
}
|
||||
})
|
||||
observer.observe(node);
|
||||
return observer;
|
||||
}
|
||||
|
||||
function unload(node) {
|
||||
setHeight(node);
|
||||
loaded = false
|
||||
}
|
||||
|
||||
function loadNode(node, handler) {
|
||||
if (loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
resetHeight(node);
|
||||
if (onload) {
|
||||
onload(node);
|
||||
}
|
||||
}
|
||||
|
||||
function addListeners(handler) {
|
||||
document.addEventListener('scroll', handler, true);
|
||||
window.addEventListener('resize', handler);
|
||||
}
|
||||
|
||||
function removeListeners(handler) {
|
||||
document.removeEventListener('scroll', handler, true);
|
||||
window.removeEventListener('resize', handler);
|
||||
}
|
||||
|
||||
function getStyleHeight() {
|
||||
return (typeof height === 'number')
|
||||
? height + 'px'
|
||||
: height;
|
||||
}
|
||||
|
||||
function setHeight(node) {
|
||||
if (height) {
|
||||
node.style.height = getStyleHeight();
|
||||
}
|
||||
}
|
||||
|
||||
function resetHeight(node) {
|
||||
setTimeout(() => {
|
||||
const isLoading = checkImgLoadingStatus(node);
|
||||
if (!isLoading) {
|
||||
node.style.height = 'auto';
|
||||
}
|
||||
// Add a delay to wait for remote resources like images to load
|
||||
}, resetHeightDelay);
|
||||
}
|
||||
|
||||
function checkImgLoadingStatus(node) {
|
||||
const img = node.querySelector('img');
|
||||
if (!img) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!img.complete) {
|
||||
contentShow = false;
|
||||
|
||||
node.addEventListener('load', () => {
|
||||
// Use auto height if loading successfully
|
||||
contentShow = true;
|
||||
node.style.height = 'auto';
|
||||
}, { capture: true, once: true });
|
||||
|
||||
node.addEventListener('error', () => {
|
||||
// Show content with fixed height if there is error
|
||||
contentShow = true;
|
||||
}, { capture: true, once: true });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (img.naturalHeight === 0) {
|
||||
// Use fixed height if img has zero height
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getContainerHeight(e) {
|
||||
if (e?.target?.getBoundingClientRect) {
|
||||
return e.target.getBoundingClientRect().bottom;
|
||||
} else {
|
||||
return window.innerHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// From underscore souce code
|
||||
function throttle(func, wait, options) {
|
||||
let context, args, result;
|
||||
let timeout = null;
|
||||
let previous = 0;
|
||||
if (!options) options = {};
|
||||
const later = function() {
|
||||
previous = options.leading === false ? 0 : new Date();
|
||||
timeout = null;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
};
|
||||
|
||||
return function(event) {
|
||||
const now = new Date();
|
||||
if (!previous && options.leading === false) previous = now;
|
||||
const remaining = wait - (now - previous);
|
||||
context = this;
|
||||
args = arguments;
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
timeout = null;
|
||||
}
|
||||
previous = now;
|
||||
result = func.apply(context, args);
|
||||
if (!timeout) context = args = null;
|
||||
} else if (!timeout && options.trailing !== false) {
|
||||
timeout = setTimeout(later, remaining);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
</script>
|
||||
15
src/components/lazy-loader/Placeholder.svelte
Normal file
15
src/components/lazy-loader/Placeholder.svelte
Normal file
@@ -0,0 +1,15 @@
|
||||
{#if placeholder}
|
||||
<div class={placeholderClass}>
|
||||
{#if typeof placeholder === 'string'}
|
||||
<div>{placeholder}</div>
|
||||
{:else if ['function', 'object'].includes(typeof placeholder)}
|
||||
<svelte:component this={placeholder} {...placeholderProps} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<script>
|
||||
export let placeholder = null;
|
||||
export let placeholderProps = null;
|
||||
const placeholderClass = 'svelte-lazy-placeholder';
|
||||
</script>
|
||||
Reference in New Issue
Block a user