class dvIT {
async suggester(values, names) {
const {SuggestModal, setIcon} = customJS.obsidian
let data = new Promise((resolve, reject) => {
this.MySuggestModal = class extends SuggestModal {
getSuggestions(query) {
return values.filter((val) => {
if (val.startsWith("[[")) {
val = val.replace(/(.*)(\/)([^\/]+)(\]\])(.*)/, "$3$5").replace(/(\[\[)(.*)(\]\])(.*)/, "$2$4")
}
return val.toLowerCase().includes(query.toLowerCase())
});
}
renderSuggestion(val, el) {
let text = val
if (names) {
text = names[values.indexOf(val)]
}
if (text.startsWith("[[")) {
text = text.replace(/(.*)(\/)([^\/]+)(\]\])(.*)/, "$3$5")
.replace(/(\[\[)(.*)(\]\])(.*)/, "$2$4")
.replace(/(.*\|)(.*)/, "$2")
let iconWrapper = el.createEl("span", { cls: "inline-icon" })
let textEl = el.createEl("span", {text: text})
setIcon(iconWrapper, "link")
} else {
el.createEl("div", {text: text})
}
}
onChooseSuggestion(val, event) {
resolve(val)
}
}
new this.MySuggestModal(app).open()
})
return data
}
async fuzzySuggester(values, names) {
const {FuzzySuggestModal, setIcon} = customJS.obsidian
let data = new Promise((resolve, reject) => {
this.MyFuzzySuggestModal = class extends FuzzySuggestModal {
getItems() {
return values
}
getItemText(val) {
let text = val
if (names) {
text = names[values.indexOf(val)]
}
if (text.startsWith("[[")) {
text = text.replace(/(.*)(\/)([^\/]+)(\]\])(.*)/, "$3$5")
.replace(/(\[\[)(.*)(\]\])(.*)/, "$2$4")
}
return text
}
renderSuggestion(val, el) {
let text = val.item
if (names) {
text = names[values.indexOf(text)]
}
if (text.startsWith("[[")) {
text = text.replace(/(.*)(\/)([^\/]+)(\]\])(.*)/, "$3$5")
.replace(/(\[\[)(.*)(\]\])(.*)/, "$2$4")
.replace(/(.*\|)(.*)/, "$2")
let iconWrapper = el.createEl("span", { cls: "inline-icon" })
let textEl = el.createEl("span", {text: text})
setIcon(iconWrapper, "link")
} else {
el.createEl("div", {text: text})
}
}
onChooseItem(val, event) {
resolve(val)
}
}
new this.MyFuzzySuggestModal(app).open()
})
return data
}
async multiSuggestDouble(header, names, values, existingValues) {
const {Modal, Setting} = customJS.obsidian
let data = new Promise((resolve, reject) => {
this.MyPromptModal = class extends Modal {
constructor(app) {
super(app);
}
onOpen() {
const {contentEl} = this
contentEl.createEl("h1", {text: header})
let include = existingValues.include
if (!include) include = []
let exclude = existingValues.exclude
if (!exclude) exclude = []
let allValues = existingValues.allValues
if (!allValues) allValues = false
this.result = {include, exclude, allValues}
new Setting(contentEl)
.setName("Result fits all values")
.addToggle((toggle) => {
toggle.setValue(this.result.allValues)
toggle.onChange((toggleValue) => {
this.result.allValues = toggleValue
})
})
new Setting(contentEl).addButton((btn) => btn
.setButtonText("Clear all")
.onClick(() => {
let toggles = document.querySelectorAll(".select-line .filter-checkbox.is-enabled")
for (let toggle of toggles) {
toggle.click()
}
}))
for (let val of values) {
let index = values.indexOf(val)
let lineClass = "select-line-" + index
let name = names[index]
new Setting(contentEl)
.setName(name)
.setClass(lineClass)
.setClass("select-line")
.addToggle((toggle) => {
if (existingValues.include.find(a => a == val)) {
toggle.setValue(true)
}
toggle.onChange((toggleValue) => {
include = include.filter(a => a != val)
if (toggleValue) {
include.push(val)
}
this.result.include = include
let excludeOn = document.querySelector("." + lineClass + " .filter-checkbox:nth-child(2).is-enabled")
if (toggleValue && excludeOn) {
excludeOn.click()
}
})
})
.addToggle((toggle) => {
if (existingValues.exclude.find(a => a == val)) {
toggle.setValue(true)
}
toggle.onChange((toggleValue) => {
exclude = exclude.filter(a => a != val)
if (toggleValue) {
exclude.push(val)
}
this.result.exclude = exclude
let includeOn = document.querySelector("." + lineClass + " .filter-checkbox:nth-child(1).is-enabled")
if (toggleValue && includeOn) {
includeOn.click()
}
})
})
let includeCheckbox = document.querySelector("." + lineClass + " .checkbox-container:nth-child(1)")
includeCheckbox.classList.add("include")
includeCheckbox.classList.add("filter-checkbox")
let excludeCheckbox = document.querySelector("." + lineClass + " .checkbox-container:nth-child(2)")
excludeCheckbox.classList.add("exclude")
excludeCheckbox.classList.add("filter-checkbox")
}
new Setting(contentEl).addButton((btn) => btn
.setButtonText("Submit")
.setCta()
.onClick(() => {
resolve(this.result)
this.close()
}))
}
onClose() {
const {contentEl} = this
contentEl.empty()
if (this.result) {
resolve(this.result)
}
reject("Task not submitted")
}
}
new this.MyPromptModal(app).open()
}).catch((e) => {console.log(e)})
return data
}
async textInput(name, defaultVal) {
if (!defaultVal) {defaultVal = ""}
const {Modal, Setting} = customJS.obsidian
let data = new Promise((resolve, reject) => {
this.MyTextInputModal = class extends Modal {
constructor(app) {
super(app);
this.eventInput = this.eventInput.bind(this)
}
eventInput(e) {
if (e.key === "Enter") {
e.preventDefault();
resolve(this.result)
this.close()
}
}
onOpen() {
const {contentEl} = this
contentEl.createEl("h1", {text: name})
const inputSetting = new Setting(contentEl)
inputSetting.settingEl.style.display = "grid";
inputSetting.addTextArea((text) => {
text.setValue(defaultVal)
this.result = defaultVal
text.onChange((value) => {
this.result = value
})
text.inputEl.style.width = "100%";
text.inputEl.rows = 10
})
new Setting(contentEl).addButton((btn) => btn
.setButtonText("Сохранить")
.setCta()
.onClick(() => {
resolve(this.result)
this.close()
}))
contentEl.addEventListener("keydown", this.eventInput)
}
onClose() {
const {contentEl} = this
contentEl.empty()
this.contentEl.removeEventListener("keydown", this.eventInput)
reject("Not submitted")
}
}
new this.MyTextInputModal(app).open()
}).catch((e) => {console.log(e)})
return data
}
async numberInput(name, defaultVal) {
if (!defaultVal && defaultVal !== 0) {defaultVal = ""}
const {Modal, Setting} = customJS.obsidian
let data = new Promise((resolve, reject) => {
this.MyNumberInputModal = class extends Modal {
constructor(app) {
super(app);
this.eventInput = this.eventInput.bind(this)
}
eventInput(e) {
if (e.key === "Enter") {
e.preventDefault();
resolve(this.result)
this.close()
}
}
onOpen() {
const {contentEl} = this
contentEl.createEl("h1", {text: name})
const inputSetting = new Setting(contentEl)
inputSetting.settingEl.style.display = "grid";
inputSetting.addButton((btn) => btn
.setButtonText("-")
.setCta()
.onClick(() => {
this.result = Number(this.result - 1)
let inputEl = contentEl.querySelector(".number-input-el")
inputEl.value = this.result + ""
}))
inputSetting.addText((text) => {
text.inputEl.type = "number"
text.inputEl.className = "number-input-el"
text.setValue(defaultVal + "")
this.result = defaultVal
text.onChange((value) => {
if (value && value != "") {
value = Number(value)
}
if (value == NaN) {
value = null
}
this.result = value
})
text.inputEl.style.width = "100%";
})
inputSetting.addButton((btn) => btn
.setButtonText("+")
.setCta()
.onClick(() => {
this.result = Number(this.result) + 1
let inputEl = contentEl.querySelector(".number-input-el")
inputEl.value = this.result + ""
}))
new Setting(contentEl).addButton((btn) => btn
.setButtonText("Сохранить")
.setCta()
.onClick(() => {
resolve(this.result)
this.close()
}))
contentEl.addEventListener("keydown", this.eventInput)
}
onClose() {
const {contentEl} = this
contentEl.empty()
this.contentEl.removeEventListener("keydown", this.eventInput)
reject("Not submitted")
}
}
new this.MyNumberInputModal(app).open()
}).catch((e) => {console.log(e)})
return data
}
async dateInput(name, defaultVal) {
if (!defaultVal) {defaultVal = ""}
const {Modal, Setting} = customJS.obsidian
let data = new Promise((resolve, reject) => {
this.MyTextInputModal = class extends Modal {
constructor(app) {
super(app);
this.eventInput = this.eventInput.bind(this)
}
eventInput(e) {
if (e.key === "Enter") {
e.preventDefault();
resolve(this.result)
this.close()
}
}
onOpen() {
const {contentEl} = this
contentEl.classList.add("date-input-modal")
contentEl.createEl("h1", {text: name})
const inputSetting = new Setting(contentEl)
inputSetting.settingEl.style.display = "grid";
inputSetting.addText((text) => {
text.setValue(defaultVal)
this.result = defaultVal
text.onChange((value) => {
this.result = value
})
text.inputEl.style.width = "100%";
text.inputEl.type = "date"
})
new Setting(contentEl).addButton((btn) => btn
.setButtonText("Сохранить")
.setCta()
.onClick(() => {
resolve(this.result)
this.close()
}))
contentEl.addEventListener("keydown", this.eventInput)
}
onClose() {
const {contentEl} = this
contentEl.empty()
this.contentEl.removeEventListener("keydown", this.eventInput)
reject("Not submitted")
}
}
new this.MyTextInputModal(app).open()
}).catch((e) => {console.log(e)})
return data
}
isLink(prop) {
if (prop && prop.path) {
return true
} else return false
}
isUrl(prop) {
if (prop && typeof prop == "string" && prop.startsWith("http")) {
return true
} else return false
}
fixList(prop) {
if (prop && !Array.isArray(prop)) {
prop = [prop]
}
return prop
}
getPropType(prop) {
let propTypes = app.metadataTypeManager.properties
let type
prop = prop.toLowerCase()
if (propTypes[prop] && propTypes[prop].type) {
type = propTypes[prop].type
}
if (prop.startsWith("file.")) type = "no filter"
if (prop == "file.path" ||
prop == "file.name" ||
prop == "file.link" ||
prop == "file.folder" ||
prop == "file.ext") {
type = "text"
} else if (prop == "file.outlinks" ||
prop == "file.inlinks" ||
prop == "file.etags" ||
prop == "file.tags" ||
prop == "file.aliases" ||
prop == "tags" ||
prop == "aliases") {
type = "multitext"
} else if (prop == "file.cday" ||
prop == "file.mday") {
type = "date"
} else if (prop == "file.ctime" ||
prop == "file.mtime") {
type = "datetime"
} else if (prop == "file.starred") {
type = "checkbox"
} else if (prop == "taskProgress" || prop == "slider") {
type = "no prop"
}
if (!type) type = "text"
return type
}
async changeViewButton(dv, container, id) {
let currentView = dv.current()["view_" + id]
if (!currentView) {
currentView = "table"
}
let icon = "table-2"
if (currentView == "list") {
icon = "list"
}
if (currentView == "cards") {
icon = "layout-grid"
}
let button = document.createElement("button")
let iconWrapper = document.createElement("span")
iconWrapper.className = "change-view-button-icon"
obsidian.setIcon(iconWrapper, icon)
button.append(iconWrapper)
button.className = "dvit-button change-view-button"
button.onclick = async () => {
await this.changeView(dv, id)
}
container.append(button)
}
async changeView(dv, id) {
let currentFile = await app.vault.getAbstractFileByPath(dv.current().file.path)
let currentView = dv.current()["view_" + id]
let views = ["table", "cards", "list"]
let view = await this.suggester(views)
if (view && view != currentView) {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter["view_" + id] = view
})
}
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
sortByProp(pages, prop, dir) {
if (prop && !prop.startsWith("file.")) {
pages = pages.sort(p => p[prop], dir)
} else if (prop && prop.startsWith("file.")) {
prop = prop.replace("file.", "")
pages = pages.sort(p => p.file[prop], dir)
}
return pages
}
async filterProps(props, current, pages, id) {
props = props.filter(prop => prop.type != "no prop")
for (let p of props) {
pages = await this.filter(p, current, pages, id)
}
let search = current["search_" + id]
if (search && search.length > 0) {
let keyWords = search.split(" ")
for (let key of keyWords) {
if (key.length > 0) {
pages = pages.filter(p => p.file.name.toLowerCase().includes(key.toLowerCase()))
}
}
}
return pages
}
async filter(p, current, filteredPages, id) {
const dv = this.dv
const getVal = this.getVal
let { prop, multiSelect } = p
let {isLink, containsPath, listContainsPath, fixList, getPropType} = this
let propType = getPropType(prop)
let propName = "filter_" + id + "_" + prop
let filter = current[propName]
if (propType == "text" || propType == "number") {
if (filter == "-") {
return filteredPages.filter(p => !getVal(p, prop))
} else if (isLink(filter)) {
return filteredPages.filter(p => containsPath(getVal(p, prop), filter.path))
} else if (filter && filter != "all") {
if (prop == "file.folder") {
return filteredPages.filter(p => getVal(p, prop).startsWith(filter))
} else return filteredPages.filter(p => getVal(p, prop) == filter)
} else {
return filteredPages
}
}
if (propType == "multitext") {
if (multiSelect) {
let includePropName = "filter_include_" + id + "_" + prop
let excludePropName = "filter_exclude_" + id + "_" + prop
let allValuesPropName = "filter_all_values_" + id + "_" + prop
let includeFilter = current[includePropName]
let excludeFilter = current[excludePropName]
let allValues = current[allValuesPropName]
if (!includeFilter) {
includeFilter = []
}
if (!excludeFilter) {
excludeFilter = []
}
if (allValues) {
for (let f of includeFilter) {
if (f == "-") {
filteredPages = filteredPages.filter(p => !getVal(p, prop) || getVal(p, prop).length == 0)
} else if (isLink(f)) {
filteredPages = filteredPages.filter(p => {
val = fixList(getVal(p, prop))
return listContainsPath (val, f.path)
})
} else if (f) {
filteredPages = filteredPages.filter(p => {
let val = getVal(p, prop)
if (!val) {return false} else {
return val.includes(f)
}
})
}
}
for (let f of excludeFilter) {
if (f == "-") {
filteredPages = filteredPages.filter(p => getVal(p, prop) && getVal(p, prop).length != 0)
} else if (isLink(f)) {
filteredPages = filteredPages.filter(p => {
let val = fixList(getVal(p, prop))
return !val || !val.find(link => link.path == f.path)
})
} else if (f) {
filteredPages = filteredPages.filter(p => {
let val = getVal(p, prop)
if (!val) {return true} else {
return !val.includes(f)
}
})
}
}
} else {
if (includeFilter.length > 0) {
filteredPages = filteredPages.map(p => {
let val = getVal(p, prop)
for (let f of includeFilter) {
if (f == "-" && (!val || val.length == 0)) {
return p
} else if (f && f.path && (val && val.find(link => link.path == f.path))){
return p
} else if (f && val && val.includes(f)) {
return p
}
}
return false
})
filteredPages = filteredPages.filter(p => p)
}
if (excludeFilter.length > 0) {
filteredPages = filteredPages.map(p => {
let val = getVal(p, prop)
for (let f of excludeFilter) {
if (f == "-" && (!val || val.length == 0)) {
return p
} else if (f.path && (!val || !val.find(link => link.path == f.path))){
return p
} else if (!val || !val.includes(f)) {
return p
}
}
return false
})
filteredPages = filteredPages.filter(p => p)
}
}
return filteredPages
} else {
if (filter == "-") {
filteredPages = filteredPages.filter(p => !getVal(p, prop) || getVal(p, prop).length == 0)
} else if (isLink(filter)) {
filteredPages = filteredPages.filter(p => {
let val = fixList(getVal(p, prop))
return listContainsPath (val, filter.path)
})
} else if (filter && filter != "all") {
filteredPages = filteredPages.filter(p => {
let val = getVal(p, prop)
if (!val) {return false} else {
return val.includes(filter)
}
})
}
return filteredPages
}
}
if (propType == "checkbox") {
if (filter == "-") {
return filteredPages.filter(p => getVal(p, prop) === undefined)
} else if (filter === false) {
return filteredPages.filter(p => getVal(p, prop) === false || getVal(p, prop) === null)
} else if (filter != "all" && filter != null) {
return filteredPages.filter(p => getVal(p, prop) == filter)
} else return filteredPages
}
if (propType == "date") {
if (filter == "-") {
return filteredPages.filter(p => getVal(p, prop) === undefined)
} else if (filter != "all" && filter != null) {
if (filter.isLuxonDateTime) {
filter = filter.toFormat("yyyy-MM-dd")
}
return filteredPages.filter(p => {
let val = getVal(p, prop)
if (val) {
return val.toFormat("yyyy-MM-dd") == filter
}
else return false
})
} else return filteredPages
}
if (propType == "datetime") {
if (filter == "-") {
return filteredPages.filter(p => getVal(p, prop) === undefined)
} else if (filter != "all" && filter != null) {
if (filter.isLuxonDateTime) {
filter = filter.toFormat("yyyy-MM-ddTHH:mm:ss")
}
return filteredPages.filter(p => {
let val = getVal(p, prop)
if (val) {
return val.toFormat("yyyy-MM-ddTHH:mm:ss") == filter
} else return false
})
} else return filteredPages
}
return filteredPages
}
containsPath(prop, text) {
if (prop && prop.path && prop.path == text) {
return true
} else return false
}
listContainsPath(prop, text) {
if (prop && Array.isArray(prop) && prop.find(p => p.path && p.path == text)) {
return true
} else if (prop && Array.isArray(prop)) {
prop = prop.filter(p => p.path)
for (let property of prop) {
if (!property.path.match(".md") && (text == property.path + ".md" || text.match("/" + property.path + ".md"))) {
return true
}
}
} else return false
}
async filterButtonProps(props, pages, container, id) {
props = props.filter(p => p.filter)
for (let p of props) {
await this.filterButton(p, pages, container, id)
}
}
async filterButton(p, pages, container, id, className) {
const {dv} = this
let {prop, multiSelect, name, buttonName} = p
if (!buttonName) {
buttonName = prop
if (name) {
buttonName = name
}
}
let current = dv.current()
let propName = "filter_" + id + "_" + prop
let buttonClass = "dvit-button"
if (!className) {
if (current[propName] && current[propName] != "all" && current[propName].length != 0) {
buttonClass = "dvit-button button-selected"
}
if (propName == "filter_" + id +"_file.tasks" && current[propName]) {
buttonClass = "dvit-button button-selected"
}
} else {
buttonClass = className
}
if (multiSelect) {
let propNameInclude = "filter_include_" + id + "_" + prop
let propNameExclude = "filter_exclude_" + id + "_" + prop
if ((current[propNameInclude] && current[propNameInclude].length != 0) ||
(current[propNameExclude] && current[propNameExclude].length != 0)) {
buttonClass = "dvit-button button-selected"
}
}
let button = document.createElement("button")
if (p.icon) {
let iconWrapper = document.createElement("span")
iconWrapper.className = "filter-button-icon"
let iconEl = obsidian.getIcon(p.icon)
if (iconEl) {
obsidian.setIcon(iconWrapper, p.icon)
} else iconWrapper.append("NO ICON")
button.append(iconWrapper)
}
button.append(buttonName)
button.className = buttonClass
button.onclick = async () => {
await this.changeProp(p, pages, id)
}
container.append(button)
}
getVal(page, prop) {
let val = page[prop]
if (prop.startsWith("file.")) {
let propLevels = prop.split(".")
val = page
for (let level of propLevels) {
val = val[level]
}
}
if (prop == "tags") val = page.file.etags
return val
}
getWikilinkPath(wikilink) {
const {dv} = this
let barelink = wikilink.replace("[[", "").replace("]]", "")
return dv.page(barelink).file.path
}
getValues(prop) {
let values = app.metadataCache.getFrontmatterPropertyValuesForKey(prop)
values.unshift("")
return values
}
getValueNames(values, propType, filter) {
let valueNames = values.map((v) => {
if (v == "all") return "-all-"
if (filter == v || (
v.startsWith("[[") && filter && filter.path == this.getWikilinkPath(v)
)
) {
v = v + " ✔"
}
return v
})
return valueNames
}
async changeProp(p, pages, id) {
let paginationProp = "pagination_" + id
const {dv} = this
const getVal = this.getVal
let { prop, multiSelect, fuzzySearch, valueOptions } = p
let current = dv.current()
let suggester = async (values, names) => {
if (fuzzySearch) {
return await this.fuzzySuggester(values, names)
} else {
return await this.suggester(values, names)
}
}
let propType = this.getPropType(prop)
if (propType == "text") {
let currentFile = await app.vault.getAbstractFileByPath(current.file.path)
let propName = "filter_" + id + "_" + prop
let filter = current[propName]
let values = pages.map(p => getVal(p, prop))
values = values.map(v => {
if (v && v.path) {
let path = v.path.replace(".md", "")
return "[[" + path + "]]"
} else return v
})
values = [...new Set(values)]
values = values.filter(v => v)
values.sort()
if (valueOptions) {
values = valueOptions.filter(v => v)
}
values.unshift("-")
values.unshift("all")
let valueNames = values.map((v) => {
if (v == "all") return "-all-"
if (filter && (filter == v || (filter.path && "[[" + filter.path.replace(".md", "") + "]]" == v))) {
v = v + " ✔"
}
return v
})
let val = await suggester(values, valueNames)
if (!val) {val = "-"}
if (val == "all") {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
delete frontmatter[propName]
frontmatter[paginationProp] = 0
})
} else {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter[propName] = val
frontmatter[paginationProp] = 0
})
}
}
if (propType == "number") {
let currentFile = await app.vault.getAbstractFileByPath(current.file.path)
let propName = "filter_" + id + "_" + prop
let values = pages.map(p => getVal(p, prop))
values = [...new Set(values)]
values = values.filter(v => v).map(v => v + "")
values.sort()
if (valueOptions) {
values = valueOptions.filter(v => v).map(v => v + "")
}
values.unshift("-")
values.unshift("all")
let val = await this.suggester(values)
if (!val) {val = "-"}
if (val == "all") {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
delete frontmatter[propName]
frontmatter[paginationProp] = 0
})
} else {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter[propName] = val
frontmatter[paginationProp] = 0
})
}
}
if (propType == "date" || propType == "datetime") {
let currentFile = await app.vault.getAbstractFileByPath(current.file.path)
let propName = "filter_" + id + "_" + prop
let values = pages.map(p => {
let val = getVal(p, prop)
if (val && propType == "date") {
val = val.toFormat("yyyy-MM-dd")
} else if (val) {
val = val.toFormat("yyyy-MM-ddTHH:mm:ss")
}
return val
})
values = [...new Set(values)]
values = values.filter(v => v)
values.sort()
values.unshift("-")
values.unshift("all")
let dateFormat = app.plugins.plugins.dataview.settings.defaultDateFormat
let locale = localStorage.getItem('language')
let valueNames = values.map(v => {
if (v == "all" || v == "-") {
return v
} else {
return dv.date(v).toFormat(dateFormat, {locale: locale})
}
})
let val = await suggester(values, valueNames)
if (!val) {val = "-"}
if (val == "all") {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
delete frontmatter[propName]
frontmatter[paginationProp] = 0
})
} else {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter[propName] = val
frontmatter[paginationProp] = 0
})
}
}
if (propType == "multitext" && !multiSelect) {
let currentFile = app.vault.getAbstractFileByPath(current.file.path)
let propName = "filter_" + id + "_" + prop
let filter = current[propName]
let values = pages.map(p => {
let val = getVal(p, prop)
if (val) return val
else return []
})
let multiValues = []
for (let v of values) {
if (!Array.isArray(v)) {
v = [v]
}
if (v) {
for (let m of v) {
multiValues.push(m)
}
}
}
multiValues = multiValues.map(v => {
if (v && v.path) {
let path = v.path.replace(".md", "")
return "[[" + path + "]]"
} else return v
})
multiValues = [...new Set(multiValues)]
multiValues = multiValues.filter(v => v)
multiValues.sort()
if (valueOptions) {
multiValues = valueOptions.filter(v => v).map(v => v + "")
}
multiValues.unshift("-")
multiValues.unshift("all")
let valueNames = multiValues.map((v) => {
let valueName
if (v == "all") valueName = "-all-"
else valueName = v
if (filter && (filter == v || (filter.path && "[[" + filter.path.replace(".md", "") + "]]" == v))) {
return valueName + " ✔"
} else return valueName
})
let val = await suggester(multiValues, valueNames)
if (!val) {val = "-"}
if (val == "all") {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
delete frontmatter[propName]
frontmatter[paginationProp] = 0
})
} else {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter[propName] = val
frontmatter[paginationProp] = 0
})
}
}
if (propType == "multitext" && multiSelect) {
let currentFile = app.vault.getAbstractFileByPath(current.file.path)
let includePropName = "filter_include_" + id + "_" + prop
let excludePropName = "filter_exclude_" + id + "_" + prop
let allValuesPropName = "filter_all_values_" + id + "_" + prop
let includeFilter = current[includePropName]
let excludeFilter = current[excludePropName]
let allValues = current[allValuesPropName]
if (!allValues && allValues !== false) {
allValues = false
}
if (!includeFilter) includeFilter = []
else {
includeFilter = includeFilter.map(f => {
if (f.path) return "[[" + f.path.replace(".md", "") + "]]"
else return f
})
}
if (!excludeFilter) excludeFilter = []
else {
excludeFilter = excludeFilter.map(f => {
if (f.path) return "[[" + f.path.replace(".md", "") + "]]"
else return f
})
}
let filter = {include: includeFilter, exclude: excludeFilter, allValues}
let values = pages.map(p => {
let val = getVal(p, prop)
if (val) return val
else return []
})
let multiValues = []
for (let v of values) {
if (!Array.isArray(v)) {
v = [v]
}
if (v) {
for (let m of v) {
multiValues.push(m)
}
}
}
multiValues = multiValues.map(v => {
if (v.path) {
let path = v.path.replace(".md", "")
return "[[" + path + "]]"
} else return v
})
multiValues = [...new Set(multiValues)]
multiValues = multiValues.filter(v => v)
multiValues.sort()
if (valueOptions) {
multiValues = valueOptions.filter(v => v)
}
multiValues.unshift("-")
let valueNames = multiValues.map((v) => {
let valueName
if (v == "all") valueName = "-all-"
else if (v.startsWith("[[")) valueName = "⩈ " + v.replace(/(.*)(\/)([^\/]+)(\]\])/, "$3").replace(/(\[\[)(.*)(\]\])/, "$2")
else valueName = v
return valueName
})
let newFilter = await this.multiSuggestDouble(name, valueNames, multiValues, filter)
if ((!newFilter.include || newFilter.include.length == 0) &&
(!newFilter.exclude || newFilter.exclude.length == 0)) {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
delete frontmatter[includePropName]
delete frontmatter[excludePropName]
delete frontmatter[allValuesPropName]
frontmatter[paginationProp] = 0
})
} else {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter[includePropName] = newFilter.include
frontmatter[excludePropName] = newFilter.exclude
frontmatter[allValuesPropName] = newFilter.allValues
frontmatter[paginationProp] = 0
})
}
}
if (propType == "checkbox") {
let currentFile = app.vault.getAbstractFileByPath(current.file.path)
let propName = "filter_" + id + "_" + prop
let values = ["all", "-", "false", "true"]
let val = await suggester(values)
if (val == "false") val = false
if (val == "true") val = true
app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter[propName] = val
frontmatter[paginationProp] = 0
})
}
if (p.prop == "file.tasks") {
let propName = "filter_" + id +"_file.tasks"
let filter = current[propName]
let currentFile = await app.vault.getAbstractFileByPath(current.file.path)
let values = ["all", "completed", "not completed"]
let valueNames = values.map((v) => {
if (v == "all") return "-all-"
if (filter == v) {
v = v + " ✔"
}
return v
})
let val = await suggester(values, valueNames)
if (val == "all") {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
delete frontmatter[propName]
})
} else {
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter[propName] = val
})
}
}
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
async newEntryButton(dv, args, container) {
const commandId = args["custom new note command"];
let button = document.createElement("button");
button.append("+");
button.className = "dvit-button";
button.onclick = async () => {
if (commandId) {
await app.commands.executeCommandById(commandId);
} else {
new Notice("未提供 command ID,无法创建笔记!");
}
};
container.append(button);
}
const { noteName, noteTemplate, noteFolder } = args
const checkIfExist = (num) => {
let numString = ""
if (num > 0) {numString = " " + num}
let path = noteName + numString + ".md"
if (noteFolder && noteFolder != "") {
path = noteFolder + "/" + noteName + numString + ".md"
}
let checkPath = app.vault.getAbstractFileByPathInsensitive(path)
if (checkPath) {
return checkIfExist(num + 1)
} else return path
}
const createNote = async () => {
let template = await app.vault.getFiles().find(f => f.basename == noteTemplate)
let data = ""
if (template) {
data = await app.vault.read(template)
}
let path = checkIfExist(0)
let file = await app.vault.create(path, data)
app.workspace.getLeaf().openFile(file)
}
let button = document.createElement("button")
button.append("+")
button.className = "dvit-button"
button.onclick = async () => {
await createNote()
}
container.append(button)
}
async refreshButton(container) {
const {dv} = this
let button = document.createElement("button")
button.append("↺")
button.className = "dvit-button"
button.onclick = async () => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}
container.append(button)
}
async searchButton(container, id) {
const {dv} = this
let current = dv.current()
let file = app.vault.getAbstractFileByPath(current.file.path)
let button = document.createElement("button")
let iconEl = obsidian.getIcon("search")
let iconWrapper = document.createElement("span")
iconWrapper.classList.add("search-button-icon")
iconWrapper.append(iconEl)
button.append(iconWrapper)
button.className = "dvit-button dvit-search-button"
if (current["show_search_" + id]) {
button.classList.add("button-selected")
}
button.onclick = async () => {
await app.fileManager.processFrontMatter(file, fm => {
if (current["show_search_" + id]) {
delete fm["search_" + id]
}
fm["show_search_" + id] = !fm["show_search_" + id]
})
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
container.append(button)
}
async searchInput(container, id) {
const {dv} = this
let current = dv.current()
let file = app.vault.getAbstractFileByPath(current.file.path)
let search = document.createElement("input")
search.classList.add("dvit-search-input")
search.value = current["search_" + id]
if (!current["search_" + id]) search.value = ""
search.addEventListener("keydown", (e) => {
if (e.key == "Enter") {
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
})
search.oninput = async (e) => {
await app.fileManager.processFrontMatter(file, fm => {
fm["search_" + id] = search.value
})
}
container.append(search)
}
async increasePagination(increase, current, paginationProp) {
let currentFile = app.vault.getAbstractFileByPath(current.file.path)
let pagination = current[paginationProp]
if (!pagination) pagination = 0
if (increase) {
pagination++
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter[paginationProp] = Number(pagination)
})
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
}
async decreasePagination(current, paginationProp) {
let currentFile = app.vault.getAbstractFileByPath(current.file.path)
let pagination = current[paginationProp]
if (!pagination) pagination = 0
if (pagination > 0) {
pagination--
await app.fileManager.processFrontMatter(currentFile, (frontmatter) => {
frontmatter[paginationProp] = Number(pagination)
})
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
}
async nextPageButton(pagesCount, paginationProp) {
const {dv} = this
let current = dv.current()
let pagination = current[paginationProp]
if (!pagination) pagination = 0
let buttonClass = "dvit-button"
let increase = true
if (pagination + 1 >= pagesCount) {
buttonClass = "dvit-button button-gray"
increase = false
}
let button = document.createElement("button")
button.append(">>")
button.className = buttonClass
button.onclick = async () => {
await this.increasePagination(increase, current, paginationProp)
}
return button
}
async prevPageButton(paginationProp) {
const {dv} = this
let current = dv.current()
let buttonClass = "dvit-button"
let pagination = current[paginationProp]
if (!pagination) pagination = 0
if (pagination <= 0) {
buttonClass = "dvit-button button-gray"
}
let button = document.createElement("button")
button.append("<<")
button.className = buttonClass
button.onclick = async () => {
await this.decreasePagination(current, paginationProp)
}
return button
}
currentPagination(paginationProp) {
const {dv} = this
let pagination = dv.current()[paginationProp]
if (!pagination) pagination = 0
return +pagination + 1
}
async paginationBlock(filteredPages, paginationNum, container, id) {
let paginationProp = "pagination_" + id
const {dv} = this
let pagesLength = filteredPages.length
let remainder = pagesLength % paginationNum
let pagesCount = (pagesLength - remainder) / paginationNum
if (remainder != 0) pagesCount++
let prev = await this.prevPageButton(paginationProp)
let next = await this.nextPageButton(pagesCount, paginationProp)
let block = document.createElement("span")
block.append(prev)
block.append(this.currentPagination(paginationProp))
block.append(next)
block.append(pagesCount)
block.style = "white-space:nowrap"
container.append(block)
}
paginate(rows, num, current, id) {
let paginationProp = "pagination_" + id
let pagination = current[paginationProp]
if (!pagination) pagination = 0
return rows.slice(num * pagination, num * (+pagination + 1))
}
joinTags(arr) {
let result = ""
if (arr == null) {
return ""
} else {
for (let a of arr) {
result += a + " "
}
return result
}
}
spanList(arr) {
let result = ""
if (arr == null) {
return ""
} else {
for (let a of arr) {
if (typeof a == "string" && !a.startsWith("<svg")) {
result += "<span class='dv-tag dv-tag-" + a.replaceAll(" ", "-") + "'>" + a + "</span> "
} else if (result.length == 0) {
result = result + a
} else result = result + ", " + a
}
return result
}
}
spanSingle(val) {
if (val == null) {
return ""
} else {
return "<span class='dv-tag dv-tag-" + val.replaceAll(" ", "-") + "'>" + val + "</span> "
}
}
taskProgress(p) {
let tasks = p.file.tasks.filter(t => t.children.length == 0)
let completed = tasks.filter(t => t.completed == true)
let max = tasks.length
let value = completed.length
if (max > 0) {
return "<div class='task-progress'><progress max=" + max + " value=" + value + "></progress> <span>" + value + " / " + max + "</span></div>"
} else return ""
}
async getCanvasPages() {
const {dv} = this
let canvases = await app.vault.getFiles().filter(f => f["extension"] == "canvas")
canvases = [...canvases]
let canvasPages = canvases.map(c => {
if (c.parent.path == "/" || !c.parent.path) {
c.folder = ""
} else {
c.folder = c.parent.path + "/"
}
c.ext = c.extension
c.name = c.basename + ".canvas"
c.link = "[[" + c.folder + c.name + "|" + c.basename + "]]"
return {file: c, type: "canvas"}
})
return dv.array(canvasPages)
}
getDisplay(link) {
let display = link.display
if (!display) {
display = link.path.replace(/(.*\/)([^/]+)(\.[^\.]+)/,"$2")
}
return display
}
async createList(props, pages, filteredPages, paginationNum, id) {
const {dv} = this
let current = dv.current()
filteredPages = await this.filterProps(props, current, filteredPages, id)
if (paginationNum) {
filteredPages = await this.paginate(filteredPages, paginationNum, current, id)
}
for (let page of filteredPages) {
let link = page.file.link
if (page.title) {
link.display = page.title
}
let propItem = props.find(p => p.prop == "link" && p.type == "file prop")
if (propItem) {
let propVal = page.file.link
if (propItem.prepend) {
let prependText = ""
if (propItem.prepend.type == "list") {
let prependItems = page[propItem.prepend.prop]
for (let item of prependItems) {
if (propItem.prepend.slice) {
item = item.slice(...propItem.prepend.slice).trim()
}
prependText = prependText + item
}
} else if (propItem.prepend.type == "text") {
prependText = page[propItem.prepend.prop]
if (propItem.prepend.slice) {
prependText = prependText.slice(...propItem.prepend.slice).trim()
}
}
link = prependText + " " + propVal
}
}
dv.paragraph(link)
}
}
async createTable(props, pages, filteredPages, paginationNum, container, id, fullWidth, cardsView) {
const {dv} = this
this.props = props
let current = dv.current()
filteredPages = await this.filterProps(props, current, filteredPages, id)
if (paginationNum) {
filteredPages = await this.paginate(filteredPages, paginationNum, current, id)
}
let tableProps = props.filter(p => p.column)
if (app.isMobile) {
tableProps = tableProps.filter(p => !p.hideOnMobile)
}
let headers = tableProps.map(propItem => {
let icon = ""
if (propItem.icon) {
let iconEl = obsidian.getIcon(propItem.icon)
if (iconEl) {
icon = iconEl.outerHTML
} else icon = "NO ICON"
}
let prop = propItem.prop
if (prop == "slider") {
prop = propItem.propVal
}
let headerButton = document.createElement("div")
headerButton.classList.add("header-sorting-button")
headerButton.setAttribute("data-prop", prop)
headerButton.setAttribute("data-view-id", id)
if (propItem.name) {
headerButton.innerHTML = icon + propItem.name
} else {
headerButton.innerHTML = icon + propItem.prop
}
return headerButton.outerHTML
})
let rows = filteredPages.map(p =>
tableProps.map(propItem => {
let propName = propItem.prop
let propType = this.getPropType(propName)
let propVal = this.getVal(p,propName)
if (propType == "multitext" && propItem.slice && propVal && Array.isArray(propVal)) {
propVal = propVal.map(p => {
if (p.path) {
let display = p.display
if (!display) {
display = p.path.replace(/(.*?)([^\/]*)(\.md)/,"$2")
}
p.display = display.slice(...propItem.slice)
} else {
p = p.slice(...propItem.slice)
}
return p
})
}
if (propType == "multitext" && propItem.replace && propVal && Array.isArray(propVal)) {
propVal = propVal.map(p => {
if (p.path) {
let display = p.display
if (!display) {
display = p.path.replace(/(.*?)([^\/]*)(\.md)/,"$2")
}
propItem.replace.forEach(r => {
if (r[2] == "icon") {
let iconEl = obsidian.getIcon(r[1])
if (iconEl) r[1] = iconEl.outerHTML.replace("svg-icon", "svg-icon dv-tag dv-tag-" + r[0])
}
p.display = display.replace(...r)
})
} else {
propItem.replace.forEach(r => {
if (r[2] == "icon") {
let iconEl = obsidian.getIcon(r[1])
if (iconEl) {
r[1] = iconEl.outerHTML.replace("svg-icon", "svg-icon dv-tag dv-tag-" + r[0])
}
}
p = p.replace(...r)
})
}
return p
})
}
if (propType == "text" && propItem.slice && propVal) {
if (propVal.path) {
let display = propVal.display
if (!display) {
display = propVal.path.replace(/(.*?)([^\/]*)(\.md)/,"$2")
}
propVal.display = display.slice(...propItem.slice)
} else {
propVal = propVal.slice(...propItem.slice)
}
}
if (Array.isArray(propVal) && propItem.span) {
propVal = this.spanList(propVal)
}
if ((propType == "text") && !Array.isArray(propVal) && propItem.span) {
propVal = this.spanSingle(propVal)
}
if ((propType == "text") && propItem.textHeight) {
if (!propVal) propVal = ""
propVal = "<div class='longText' style='max-height: " + propItem.textHeight + "px'>" + propVal + "</div>"
}
if (propItem.image) {
let imageWidth = 100
if (propItem.width) imageWidth = propItem.width
if ( propVal && propVal.path && !propVal.path.endsWith(".md")) {
propVal = dv.fileLink(propVal.path, true, imageWidth)
} else if (propVal && !propVal.path && propVal.startsWith("http")) {
propVal = ""
} else propVal = ""
}
if (propType == "file prop") {
propVal = p.file[propName]
if (propVal && propItem.slice) {
if (propVal.path) {
let display = propVal.display
if (!display) {
display = propVal.path.replace(/(.*?)([^\/]*)(\.md)/,"$2")
}
propVal.display = display.slice(...propItem.slice)
} else {
propVal = propVal.slice(...propItem.slice)
}
}
if (propItem.prepend) {
let prependText = ""
if (propItem.prepend.type == "list") {
let prependItems = p[propItem.prepend.prop]
for (let item of prependItems) {
if (propItem.prepend.slice) {
item = item.slice(...propItem.prepend.slice).trim()
}
prependText = prependText + item
}
} else if (propItem.prepend.type == "text") {
prependText = p[propItem.prepend.prop]
if (propItem.prepend.slice) {
prependText = prependText.slice(...propItem.prepend.slice).trim()
}
}
propVal = prependText + " " + propVal
}
}
if (propName == "tags") {
propVal = this.joinTags(p.file.etags)
}
if (propName == "taskProgress") {
propVal = this.taskProgress(p)
}
let editButton = document.createElement("div")
editButton.classList.add("edit-button")
if (!propName.startsWith("file.")) {
if (propItem.editButton == "select" && (propType == "text" || propType == "multitext")) {
editButton.setAttribute('data-type', 'select')
}
if (propType == "text" && propItem.editButton != "select") {
editButton.setAttribute('data-type', 'text')
}
if (propType == "multitext" && propItem.editButton != "select") {
editButton.setAttribute('data-type', 'list')
}
if (propType == "date") {
editButton.setAttribute('data-type', 'date')
if (propVal) {
let dateFormat = app.plugins.plugins.dataview.settings.defaultDateFormat
let locale = localStorage.getItem('language')
propVal = propVal.toFormat(dateFormat, {locale: locale})
}
}
if (propType == "number") {
editButton.setAttribute('data-type', 'number')
}
}
if (propName == "file.link" || propName == "file.name") {
editButton.setAttribute('data-type', 'file name')
}
editButton.setAttribute('data-path', p.file.path)
editButton.setAttribute('data-prop', propName)
if (Array.isArray(propVal)) {
let propArr = document.createElement("ul")
for (let propElData of propVal) {
let propEl = document.createElement("li")
propEl.append(propElData)
propArr.append(propEl)
}
propVal = propArr.outerHTML
}
editButton.innerHTML = propVal
if (!editButton.innerHTML || editButton.innerHTML == "null" || editButton.innerHTML == "undefined") {
editButton.innerHTML = propName
editButton.classList.add("edit-button-empty")
}
propVal = editButton
if (propType == "checkbox") {
propVal = '<input type="checkbox" class="prop-checkbox" data-path="' + p.file.path + '" data-prop="' + propName + '">'
}
if (propName == "slider") {
let propSliderVal = propItem.propVal
let propMax = propItem.propMax
let max = p[propMax]
if (max) {
let slider = '<input type="range" max=' + p[propMax] + ' step="1" class="slider table-property-slider" data-path="' + p.file.path + '" data-prop="' + propSliderVal + '" data-prop="' + propMax + '">'
let sliderWrapper = document.createElement("div")
sliderWrapper.innerHTML = slider
propVal = sliderWrapper
} else {
propVal.innerHTML = "Slider"
propVal.classList.add("edit-button-empty")
propVal.setAttribute('data-type', 'number')
propVal.setAttribute('data-prop', propMax)
}
}
if (propItem.alignBottom) {
propVal.classList.add("align-bottom")
}
if (propName == "file.tasks") {
if (p.file && p.file.tasks) {
let tasks = p.file.tasks
let filterStatus = dv.current()["filter_" + id +"_file.tasks"]
if (filterStatus == "completed") {
tasks = tasks.filter(t => t.status == "x")
}
if (filterStatus == "not completed") {
tasks = tasks.filter(t => t.status == " ")
}
let taskList = document.createElement("ul")
taskList.className = "contains-task-list has-list-bullet"
for (let task of tasks) {
let taskLine = document.createElement("li")
taskLine.classList.add("task-list-item")
taskLine.setAttribute('data-task', task.status)
let checkbox = document.createElement("input")
checkbox.type = "checkbox"
checkbox.classList.add("task-list-item-checkbox")
checkbox.classList.add("file-task-checkbox")
checkbox.setAttribute('data-path', p.file.path)
checkbox.setAttribute('data-task-line', task.line)
if (task.checked) {
taskLine.classList.add("is-checked")
checkbox.setAttribute('checked','checked')
}
taskLine.append(checkbox)
taskLine.append(task.text.replaceAll("\n", ""))
taskList.append(taskLine)
}
propVal = taskList
}
}
return propVal
}))
let markdownTable = dv.markdownTable(headers, rows)
markdownTable = markdownTable.replaceAll("&", "&")
let tableWrapper = dv.paragraph(markdownTable)
container.append(tableWrapper)
tableWrapper.classList.add("dv-table-wrapper")
if (fullWidth) {
tableWrapper.classList.add("full-width")
}
if (!cardsView) {
tableWrapper.classList.add("table")
}
if (cardsView) {
tableWrapper.classList.add("cards")
if (cardsView.position) {
tableWrapper.classList.add("cards-" + cardsView.position)
}
}
let editButtons = document.querySelectorAll(".edit-button")
for (let button of editButtons) {
button.onclick = async (event) => {
if (event.target.localName != "a") {
let path = button.getAttribute("data-path")
let prop = button.getAttribute("data-prop")
let type = button.getAttribute("data-type")
await this.editProp(type, path, prop, dv)
}
}
}
let sliders = document.querySelectorAll(".table-property-slider")
for (let slider of sliders) {
let prop = slider.getAttribute("data-prop")
let path = slider.getAttribute("data-path")
let page = dv.page(path)
let val = page[prop]
if (!val) val = 0
slider.value = val
this.updateSlider(slider)
slider.oninput = async () => {
this.updateSlider(slider)
await this.editSlider(slider)
}
slider.ondblclick = async () => {
await this.editSliderVal(slider, dv)
}
}
let checkboxes = document.querySelectorAll(".prop-checkbox")
for (let checkbox of checkboxes) {
let prop = checkbox.getAttribute("data-prop")
let path = checkbox.getAttribute("data-path")
let page = dv.page(path)
checkbox.checked = page[prop]
checkbox.onchange = async() => {
let file = app.vault.getAbstractFileByPath(path)
await app.fileManager.processFrontMatter(file, (frontmatter) => {
frontmatter[prop] = checkbox.checked
})
}
}
let taskCheckboxes = document.querySelectorAll(".file-task-checkbox")
for (let checkbox of taskCheckboxes) {
checkbox.onchange = async() => {
let path = checkbox.getAttribute("data-path")
let lineNum = checkbox.getAttribute("data-task-line")
let file = app.vault.getAbstractFileByPath(path)
let content = await app.vault.cachedRead(file)
let lines = content.split("\n")
let line = lines[lineNum]
if (checkbox.checked) {
line = line.replace("- [ ]", "- [x]")
} else {
line = line.replace("- [x]", "- [ ]")
}
lines[lineNum] = line
let newContent = lines.join("\n")
await app.vault.modify(file, newContent)
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
}
let headerButtons = document.querySelectorAll(".header-sorting-button")
for (let button of headerButtons) {
let prop = button.getAttribute("data-prop")
let id = button.getAttribute("data-view-id")
let sortProp = "sort_" + id
let sortDirProp = "sort_direction_" + id
let file = app.vault.getAbstractFileByPath(dv.current().file.path)
button.onclick = async () => {
await app.fileManager.processFrontMatter(file, fm => {
if (fm[sortProp] == prop) {
if (!fm[sortDirProp] || fm[sortDirProp] == "desc") {
fm[sortDirProp] = "asc"
} else {
fm[sortDirProp] = "desc"
}
} else {
fm[sortProp] = prop
fm[sortDirProp] = "asc"
}
})
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
button.onmousedown = async (e) => {
if (e.button == 2) {
await app.fileManager.processFrontMatter(file, fm => {
delete fm[sortProp]
delete fm[sortDirProp]
})
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
}
}
}
updateSlider (slider) {
let setTooltip = customJS.obsidian.setTooltip
let value = slider.value
let max = slider.max
let percents = this.getSliderPercents(value, max)
let imageColor = "var(--color-red)"
if (percents > 25) imageColor = "var(--color-yellow)"
if (percents > 50) imageColor = "var(--color-green)"
if (percents > 75) imageColor = "var(--color-cyan)"
if (percents == 100) imageColor = "var(--color-purple)"
slider.style.backgroundSize = percents + "% 100%"
slider.style.backgroundImage = "linear-gradient(" + imageColor + ", " + imageColor + ")"
setTooltip(slider, value, {placement: "top", delay: 1})
}
getSliderPercents (value, max) {
let val = value
if (!val) val = 0
if (!max) {
val = 0
max = 0
}
if (Number(val) > Number(max)) {
val = max
}
return Math.round(val * 100 / max)
}
async editSlider (slider) {
let path = slider.getAttribute("data-path")
let prop = slider.getAttribute("data-prop")
let file = app.vault.getAbstractFileByPath(path)
await app.fileManager.processFrontMatter(file, (fm) => {
fm[prop] = Number(slider.value)
})
}
async editSliderVal (slider, dv) {
let path = slider.getAttribute("data-path")
let prop = slider.getAttribute("data-prop")
await this.editProp("number", path, prop, dv)
}
async editProp (type, path, prop, dv) {
let page = dv.page(path)
let file = app.vault.getAbstractFileByPath(path)
if (type == "file name") {
let prevName = file.basename
let newName = await this.textInput("Title", prevName)
if (!newName) newName = prevName
let folder = file.parent.path
let ext = file.extension
let newPath = newName + "." + ext
if (folder != "/") {
newPath = folder + "/" + newName + "." + ext
}
await app.vault.rename(file, newPath)
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
return
}
let prevVal = page[prop]
if (this.isLink(prevVal)) {
prevVal = "[[" + prevVal.path + "|" + this.getDisplay(prevVal) + "]]"
}
if (Array.isArray(prevVal)) {
prevVal = prevVal.map(p => {
if (this.isLink(p)) {
p = "[[" + p.path + "|" + this.getDisplay(p) + "]]"
}
return p
})
}
let val
if (type == "text") {
val = await this.textInput(prop, prevVal)
if (val === undefined) val = prevVal
} else if (type == "list") {
let values = this.getValues(prop)
if (!values) values = []
let propItem = this.props.find(p => p.prop == prop)
let options = propItem.valueOptions
if (options) {
values = [...options]
}
if (!prevVal) prevVal = []
if (typeof prevVal == "string") prevVal = [prevVal]
let addedValues = [...prevVal]
let notAddedValues = values.filter(v => {
for (let a of addedValues) {
if (v == a) return false
}
if (v == "") return false
return true
})
notAddedValues.unshift("+ add new option")
let command = await this.suggester(["add", "remove"])
if (command == "add") {
let addVal = await this.suggester(notAddedValues)
if (addVal && addVal != "+ add new option") {
addedValues.push(addVal)
} else if (addVal == "+ add new option") {
addVal = await this.textInput(prop, "")
if (addVal) {
addedValues.push(addVal)
}
}
} else if (command == "remove") {
let removeVal = await this.suggester(addedValues)
if (removeVal) {
addedValues = addedValues.filter(a => a != removeVal)
}
}
val = addedValues
} else if (type == "select") {
let values = this.getValues(prop)
let propItem = this.props.find(p => p.prop == prop)
let options = propItem.valueOptions
if (options) {
values = [...options]
}
values.unshift("+ add new option")
let valueNames = values.map(v => {
if (v == "") return "-"
else return v
})
val = await this.suggester(values, valueNames)
if (val == "+ add new option") {
val = await this.textInput(prop, "")
if (val === undefined) val = prevVal
}
let propType = this.getPropType(prop)
if (propType == "multitext") {
if (val != "") {
val = [val]
} else {
val = []
}
}
} else if (type == "number") {
val = await this.numberInput(prop, prevVal)
if (val === undefined) val = prevVal
} else if (type == "date") {
if (prevVal) {
prevVal = prevVal.toISODate()
}
val = await this.dateInput(prop, prevVal)
if (val === undefined) val = prevVal
if (val == "") val = null
}
if (val !== prevVal) {
await app.fileManager.processFrontMatter(file, fm => {
fm[prop] = val
})
setTimeout(async() => {
await app.commands.executeCommandById("dataview:dataview-force-refresh-views")
}, 250)
}
}
async renderView (settings, props, pages, dv) {
let id = settings["id"]
if (!id) id = "no-id"
let viewContainer = document.createElement("div")
viewContainer.classList.add("dvit-view-id-" + id)
dv.container.append(viewContainer)
let sortProp = dv.current()["sort_" + id]
let sortDir = dv.current()["sort_direction_" + id]
if (!sortDir) sortDir = "asc"
pages = this.sortByProp(pages, sortProp, sortDir)
let view = dv.current()["view_" + id]
let cardsPosition = settings["cards image position"]
let paginationNum = settings["entries on page"]
let addNewButton = settings["add new note button"]
let fullWidth = settings["full width"]
this.dv = dv
if (addNewButton) {
let noteName = settings["new note name"]
let noteTemplate = settings["new note template"]
let noteFolder = settings["new note folder"]
if (!noteName) noteName = "New note"
let args = {
noteName,
noteTemplate,
noteFolder
}
await this.newEntryButton(dv, args, viewContainer)
}
let filteredPages = [...pages]
filteredPages = await this.filterProps(props, dv.current(), filteredPages, id)
await this.filterButtonProps(props, pages, viewContainer, id)
await this.changeViewButton(dv, viewContainer, id)
await this.refreshButton(viewContainer)
if (paginationNum) {
await this.paginationBlock(filteredPages, paginationNum, viewContainer, id)
}
await this.searchButton(viewContainer, id)
if (dv.current()["show_search_" + id]) {
await this.searchInput(viewContainer, id)
}
if (!view || view == "table") {
await this.createTable(props, pages, filteredPages, paginationNum, viewContainer, id, fullWidth)
} else if (view == "cards") {
await this.createTable(props, pages, filteredPages, paginationNum, viewContainer, id, fullWidth, {cards: true, position: cardsPosition})
} else if (view == "list") {
await this.createList(props, pages, filteredPages, paginationNum, id)
}
let search = document.querySelector(".dvit-search-input")
if(search) {
search.focus()
}
}
}