Preview:
```dataviewjs
class PersistentData {
	static #constructKey = Symbol()
	static #instance
	static _dataFilePath
	static _defaultData
	_dataFilePath
	_defaultData
	_data
	constructor(constructKey, dataFilePath, defaultData) {
		if (constructKey !== PersistentData.#constructKey)
			throw new Error('constructor is private')
		this._dataFilePath = dataFilePath
		this._defaultData = defaultData
	}
	static setConfig(dataFilePath, dafaultData){
		this._dataFilePath = dataFilePath
		this._defaultData = dafaultData
	}
	static async getInstance(){
		if (!this.#instance){
			this.#instance = new PersistentData(
				this.#constructKey,
				this._dataFilePath,
				this._defaultData
			)
			await this.#instance.initialize()
		}
		return this.#instance
	}
	async initialize(){
		const exists = await app.vault.adapter.exists(this._dataFilePath)
		if (exists) {
			const str = await app.vault.adapter.read(this._dataFilePath)
			const data = JSON.parse(str)
			// 保存されているデータとdefaultDataのキーが不一致の場合、データの仕様が変わったと判断し、保存データをdefaultDataにリセットする
			if (this._haveSameKeys(data, this._defaultData)){
				this._data = data
				return
			}
		}
		this._data = this._defaultData
		await this.saveData()
	}
	getData(key){
		return this._data[key]
	}
	async setData(key, value){
		this._data[key] = value
		await this.saveData()
	}
	async saveData(){
		await app.vault.adapter.write(this._dataFilePath, this._toJSON(this._data))
	}
	_haveSameKeys(obj1, obj2) {
		const keys1 = Object.keys(obj1).sort()
		const keys2 = Object.keys(obj2).sort()
		return keys1.length === keys2.length && keys1.every((key, index) => key === keys2[index])
	}
	_toJSON(obj){
		return JSON.stringify(obj, null, 2)
	}
}

class DomUtils {
	static #registry = new Map()
	static setRegistry(key, element){
		if (this.#registry.has(key))
			throw new Error(`Element with key "${key}" is already registered`)
		if (!(element instanceof HTMLElement))
			throw new Error('Invalid element')
		this.#registry.set(key, element)
	}
	static getRegistry(key){
		return this.#registry.get(key) || null
	}
	static createElement(tagName, attributes = {}){
		const elm = document.createElement(tagName)
		if (attributes.container instanceof HTMLElement)
			attributes.container.appendChild(elm)
		if (attributes.data){
			Object.entries(attributes.data).forEach(([key, value]) => {
				elm.setAttribute(`data-${key}`, value)
			})
		}
		if (attributes.registryKey)
			this.setRegistry(attributes.registryKey, elm)
		if (attributes.onClick)
			elm.addEventListener('click', attributes.onClick)
		if (attributes.onChange)
			elm.addEventListener('change', attributes.onChange)
		if (attributes.onInput)
			elm.addEventListener('input', attributes.onInput)
		return elm
	}
	static createSelectElement(options = [], defaultValue = null, attributes = {}){
		const elm = this.createElement('select', attributes)
		const optionElms = this.createSelectOptions(options, defaultValue)
		elm.appendChild(optionElms)
		return elm
	}
	static createSelectOptions(options = [], defaultValue = null){
		const fragment = document.createDocumentFragment()
		options.forEach(option => {
			const optionElm = document.createElement('option')
			optionElm.value = option.value
			optionElm.textContent = option.text
			if (option.value === defaultValue)
				optionElm.selected = true
			fragment.appendChild(optionElm)
		})
		return fragment
	}
	static replaceSelectOptions(selectElement, options, defaultValue = null){
		const optionElms = this.createSelectOptions(options, defaultValue)
		selectElement.replaceChildren(optionElms)
	}
	static createInputElement(type = 'text', value = '', attributes = {}){
		const elm = this.createElement('input', attributes)
		elm.type = type
		elm.value = value
		if (attributes.placeholder)
			elm.placeholder = attributes.placeholder
		return elm
	}
	static createAnchorElement(href = '#', text = '', attributes = {}){
		const elm = this.createElement('a', attributes)
		elm.href = href
		elm.textContent = text
		if (attributes.target)
			elm.target = attributes.target
		return elm
	}
}

class ViewUtils {
	static clearScreen(){
		dv.container.replaceChildren()
	}
	static createSelectDays(){
		DomUtils.createSelectElement(
			config.selectDaysOptions.map(v => ({value:v, text:`${v}日`})),
			(v => {
				return config.selectDaysOptions.includes(v) ? v : config.persistentData.defaultData.selectDaysValue
			})(persistentData.getData('selectDaysValue')),
			{
				container: dv.container,
				registryKey: 'selectDays',
				onChange: async (e) => {
					persistentData.setData('selectDaysValue', e.target.value),
					ViewUtils.updateList()
				}
			}
		)
	}
	static createSelectFolder(){
		DomUtils.createSelectElement(
			[],
			null,
			{
				container: dv.container,
				registryKey: 'selectFolder',
				onChange: async (e) => {
					persistentData.setData('currentFolder', e.target.value)
					ViewUtils.updateList()
				}
			}
		)
	}
	static createInputSearch(){
		DomUtils.createInputElement(
			'text',
			persistentData.getData('searchString'),
			{
				placeholder: '検索',
				container: dv.container,
				registryKey: 'searchInput',
				onInput: debounce(async (e) => {
					persistentData.setData('searchString', e.target.value.trim())
					ViewUtils.updateList()
				}, 1000)
			}
		)
	}
	static createListContainer(){
		DomUtils.createElement(
			'div',
			{
				container: dv.container,
				registryKey: 'listContainer',
				onClick: (e) => {
					if (e.target.tagName === 'A')
						app.workspace.openLinkText(e.target.dataset.url, '', true)
				}
			}
		)
	}
	static async updateList() {
		const daysBack = persistentData.getData('selectDaysValue')
		let currentFolder = persistentData.getData('currentFolder')
		const searchString = persistentData.getData('searchString')
		const now = dv.date('now')
		const startDate = now.minus({days: parseInt(daysBack) - 1}).startOf('day')
		let pages = dv.pages()
			.where(p => {
				const mtime = dv.date(p.file.mtime)
				return mtime >= startDate && mtime <= now
			})
		if (searchString)
			pages = pages.where(p => p.file.name.toLowerCase().includes(searchString.toLowerCase()))

		const folderCounts = {}
		pages.forEach(page => {
			const folder = page.file.folder || ''
			folderCounts[folder] = (folderCounts[folder] || 0) + 1
		})
		const folders = Array.from(pages.file.folder.distinct()).sort()
		folders.unshift('すべてのフォルダ')

		if (!folders.includes(currentFolder)){
			currentFolder = 'すべてのフォルダ'
			persistentData.setData('currentFolder', currentFolder)
		}

		DomUtils.replaceSelectOptions(
			DomUtils.getRegistry('selectFolder'),
			folders.map(folder => {
				let count, text
				if (folder === 'すべてのフォルダ'){
					count = pages.length
					text = `${folder} (${count})`
				} else {
					count = folderCounts[folder]
					text = `/${folder} (${count})`
				}
				return {value:folder, text:text}
			}),
			currentFolder
		)

		if (currentFolder !== 'すべてのフォルダ')
			pages = pages.where(p => p.file.folder === currentFolder)

		const ul = DomUtils.createElement('ul')
		pages
			.sort(p => p.file.mtime, 'desc')
			.forEach(page => {
				const li = DomUtils.createElement('li')
				DomUtils.createAnchorElement('#', page.file.name, {container: li, data:{url:page.file.name}})
				ul.appendChild(li)
			})
		DomUtils.getRegistry('listContainer').replaceChildren(ul)
	}
}

function debounce(func, wait) {
	let timeout = null
	return function(...args) {
		if (timeout)
			clearTimeout(timeout)
		timeout = setTimeout(() => {
			func(...args)
			timeout = null
		}, wait)
	}
}

const config = {
	persistentData: {
		// 永続データ保存ファイルのVault内パス
		// 親ディレクトリは前もって作成しておく
		dataFilePath: '/Scripts/Dataview/PersistentData/touchSort.json',

		// 永続データのデフォルト値
		defaultData: {selectDaysValue: '10', currentFolder: 'すべてのフォルダ', searchString: ''}
	},
	// 日数の選択肢
	selectDaysOptions: ['1', '2', '3', '5', '7', '10', '14', '20', '30', '60', '90', '120']
}

PersistentData.setConfig(
	config.persistentData.dataFilePath,
	config.persistentData.defaultData
)
const persistentData = await PersistentData.getInstance()

// 稀に古い要素が残るので明示的に画面をクリアする
ViewUtils.clearScreen()

ViewUtils.createSelectDays()
ViewUtils.createSelectFolder()
ViewUtils.createInputSearch()
ViewUtils.createListContainer()

ViewUtils.updateList()

```
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter