```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)
}
}
function debounce(func, wait) {
let timeout = null
return function(...args) {
if (timeout)
clearTimeout(timeout)
timeout = setTimeout(() => {
func(...args)
timeout = null
}, wait)
}
}
async function updateList() {
listContainer.innerHTML = ''
const daysBack = selectDays.value
const now = dv.date('now')
let pages
const startDate = now.minus({ days: parseInt(daysBack) - 1 }).startOf('day')
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 = 'すべてのフォルダ'
await persistentData.setData('currentFolder', currentFolder)
}
selectFolder.innerHTML = ''
folders.forEach(folder => {
const count = folder === 'すべてのフォルダ' ? pages.length : (folderCounts[folder] || 0)
const displayText = folder === 'すべてのフォルダ' ? `${folder} (${count})` : `${folder} (${count})`
const option = dv.el('option', displayText, { container: selectFolder })
option.value = folder
if (folder === currentFolder)
option.selected = true
})
let filteredPages = pages
if (currentFolder !== 'すべてのフォルダ')
filteredPages = pages.where(p => p.file.folder === currentFolder)
const ul = dv.el('ul', '', { container: listContainer })
filteredPages
.sort(p => p.file.mtime, 'desc')
.forEach((page) => {
const li = dv.el('li', '', { container: ul })
const a = dv.el('a', page.file.name, { container: li })
a.addEventListener('click', () => {
app.workspace.openLinkText(page.file.path, '', true)
})
})
}
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]
}
PersistentData.setConfig(
config.persistentData.dataFilePath,
config.persistentData.defaultData
)
const persistentData = await PersistentData.getInstance()
let currentFolder = persistentData.getData('currentFolder')
let searchString = persistentData.getData('searchString')
const selectDays = dv.el('select', '')
const options = config.selectDaysOptions
options.forEach(option => {
const opt = dv.el('option', `${option}日`, { container: selectDays })
opt.value = option
})
const selectFolder = dv.el('select', '')
const searchInput = dv.el('input', '', { attr: { type: 'text', placeholder: '検索', value: searchString} })
const listContainer = dv.el('div', '')
selectDays.addEventListener('change', async () => {
updateList()
await persistentData.setData('selectDaysValue', selectDays.value)
})
selectFolder.addEventListener('change', async () => {
currentFolder = selectFolder.value
updateList()
await persistentData.setData('currentFolder', currentFolder)
})
searchInput.addEventListener('input', debounce(async () => {
searchString = searchInput.value.trim()
updateList()
await persistentData.setData('searchString', searchString)
}, 1000))
selectDays.value = persistentData.getData('selectDaysValue')
selectFolder.value = currentFolder
updateList()
```
Preview:
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