Work in progress
This commit is contained in:
391
client/components/open/tables/OpenTable.vue
Normal file
391
client/components/open/tables/OpenTable.vue
Normal file
@@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<table :id="'table-'+tableHash" ref="table"
|
||||
class="notion-table n-table whitespace-no-wrap bg-white dark:bg-notion-dark-light relative"
|
||||
>
|
||||
<thead :id="'table-header-'+tableHash" ref="header"
|
||||
class="n-table-head top-0"
|
||||
:class="{'absolute': data.length !== 0}"
|
||||
style="will-change: transform; transform: translate3d(0px, 0px, 0px)"
|
||||
>
|
||||
<tr class="n-table-row overflow-x-hidden">
|
||||
<resizable-th v-for="col, index in form.properties" :id="'table-head-cell-' + col.id" :key="col.id"
|
||||
scope="col" :allow-resize="allowResize" :width="(col.cell_width ? col.cell_width + 'px':'auto')"
|
||||
class="n-table-cell p-0 relative"
|
||||
@resize-width="resizeCol(col, $event)"
|
||||
>
|
||||
<p
|
||||
:class="{'border-r': index < form.properties.length - 1 || hasActions}"
|
||||
class="bg-gray-50 dark:bg-notion-dark truncate sticky top-0 border-b border-gray-200 dark:border-gray-800 px-4 py-2 text-gray-500 font-semibold tracking-wider uppercase text-xs"
|
||||
>
|
||||
{{ col.name }}
|
||||
</p>
|
||||
</resizable-th>
|
||||
<th v-if="hasActions" class="n-table-cell p-0 relative" style="width: 100px">
|
||||
<p
|
||||
class="bg-gray-50 dark:bg-notion-dark truncate sticky top-0 border-b border-gray-200 dark:border-gray-800 px-4 py-2 text-gray-500 font-semibold tracking-wider uppercase text-xs"
|
||||
>
|
||||
Actions
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody v-if="data.length > 0" class="n-table-body bg-white dark:bg-notion-dark-light">
|
||||
<tr v-if="$slots.hasOwnProperty('actions')"
|
||||
:id="'table-actions-'+tableHash"
|
||||
ref="actions-row"
|
||||
class="action-row absolute w-full"
|
||||
style="will-change: transform; transform: translate3d(0px, 32px, 0px)"
|
||||
>
|
||||
<td :colspan="form.properties.length" class="p-1">
|
||||
<slot name="actions" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-for="row, index in data" :key="index" class="n-table-row" :class="{'first':index===0}">
|
||||
<td v-for="col, colIndex in form.properties"
|
||||
:key="col.id"
|
||||
:style="{width: col.cell_width + 'px'}"
|
||||
class="n-table-cell border-gray-100 dark:border-gray-900 text-sm p-2 overflow-hidden"
|
||||
:class="[{'border-b': index !== data.length - 1, 'border-r': colIndex !== form.properties.length - 1 || hasActions},
|
||||
colClasses(col)]"
|
||||
>
|
||||
<component :is="fieldComponents[col.type]" class="border-gray-100 dark:border-gray-900"
|
||||
:property="col" :value="row[col.id]"
|
||||
/>
|
||||
</td>
|
||||
<td v-if="hasActions" class="n-table-cell border-gray-100 dark:border-gray-900 text-sm p-2 border-b"
|
||||
style="width: 100px"
|
||||
>
|
||||
<record-operations :form="form" :structure="form.properties" :rowid="row.id" @deleted="$emit('deleted')" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="loading" class="n-table-row border-t bg-gray-50 dark:bg-gray-900">
|
||||
<td :colspan="form.properties.length" class="p-8 w-full">
|
||||
<loader class="w-4 h-4 mx-auto" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody v-else key="body-content" class="n-table-body">
|
||||
<tr class="n-table-row loader w-full">
|
||||
<td :colspan="form.properties.length" class="n-table-cell w-full p-8">
|
||||
<loader v-if="loading" class="w-4 h-4 mx-auto" />
|
||||
<p v-else class="text-gray-500 text-center">
|
||||
No data found.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useWorkingFormStore } from '../../../stores/working_form'
|
||||
import OpenText from './components/OpenText.vue'
|
||||
import OpenUrl from './components/OpenUrl.vue'
|
||||
import OpenSelect from './components/OpenSelect.vue'
|
||||
import OpenDate from './components/OpenDate.vue'
|
||||
import OpenFile from './components/OpenFile.vue'
|
||||
import OpenCheckbox from './components/OpenCheckbox.vue'
|
||||
import ResizableTh from './components/ResizableTh.vue'
|
||||
import RecordOperations from '../components/RecordOperations.vue'
|
||||
import clonedeep from 'clone-deep'
|
||||
|
||||
const cyrb53 = function (str, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed
|
||||
let h2 = 0x41c6ce57 ^ seed
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i)
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761)
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677)
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909)
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909)
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
|
||||
}
|
||||
|
||||
export default {
|
||||
components: { ResizableTh, RecordOperations },
|
||||
props: {
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
allowResize: {
|
||||
required: false,
|
||||
default: true,
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
|
||||
setup () {
|
||||
const workingFormStore = useWorkingFormStore()
|
||||
return {
|
||||
workingFormStore
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
tableHash: null,
|
||||
skip: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
form: {
|
||||
get () {
|
||||
return this.workingFormStore.content
|
||||
},
|
||||
/* We add a setter */
|
||||
set (value) {
|
||||
this.workingFormStore.set(value)
|
||||
}
|
||||
},
|
||||
hasActions () {
|
||||
// In future if want to hide based on condition
|
||||
return true
|
||||
},
|
||||
fieldComponents () {
|
||||
return {
|
||||
text: OpenText,
|
||||
number: OpenText,
|
||||
select: OpenSelect,
|
||||
multi_select: OpenSelect,
|
||||
date: OpenDate,
|
||||
files: OpenFile,
|
||||
checkbox: OpenCheckbox,
|
||||
url: OpenUrl,
|
||||
email: OpenText,
|
||||
phone_number: OpenText,
|
||||
signature: OpenFile
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
'form.properties': {
|
||||
handler () {
|
||||
this.onStructureChange()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
data () {
|
||||
this.$nextTick(() => {
|
||||
this.handleScroll()
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
const parent = document.getElementById('table-page')
|
||||
this.tableHash = cyrb53(JSON.stringify(this.form.properties))
|
||||
if (parent) {
|
||||
parent.addEventListener('scroll', this.handleScroll, { passive: true })
|
||||
}
|
||||
window.addEventListener('resize', this.handleScroll)
|
||||
this.onStructureChange()
|
||||
this.handleScroll()
|
||||
},
|
||||
|
||||
beforeUnmount () {
|
||||
const parent = document.getElementById('table-page')
|
||||
if (parent) {
|
||||
parent.removeEventListener('scroll', this.handleScroll)
|
||||
}
|
||||
window.removeEventListener('resize', this.handleScroll)
|
||||
},
|
||||
|
||||
methods: {
|
||||
colClasses (col) {
|
||||
let colAlign, colColor, colFontWeight, colWrap
|
||||
|
||||
// Column align
|
||||
colAlign = `text-${col.alignment ? col.alignment : 'left'}`
|
||||
|
||||
// Column color
|
||||
colColor = null
|
||||
if (!col.hasOwnProperty('color') || col.color === 'default') {
|
||||
colColor = 'text-gray-700 dark:text-gray-300'
|
||||
}
|
||||
colColor = `text-${col.color}`
|
||||
|
||||
// Column font weight
|
||||
if (col.hasOwnProperty('bold') && col.bold) {
|
||||
colFontWeight = 'font-semibold'
|
||||
}
|
||||
|
||||
// Column wrapping
|
||||
if (!col.hasOwnProperty('wrap_text') || !col.wrap_text) {
|
||||
colWrap = 'truncate'
|
||||
}
|
||||
|
||||
return [colAlign, colColor, colWrap, colFontWeight]
|
||||
},
|
||||
onStructureChange () {
|
||||
if (this.form && this.form.properties) {
|
||||
this.$nextTick(() => {
|
||||
this.form.properties.forEach(col => {
|
||||
if (!col.hasOwnProperty('cell_width')) {
|
||||
if (this.allowResize && this.form !== null && document.getElementById('table-head-cell-' + col.id)) {
|
||||
// Within editor
|
||||
this.resizeCol(col, document.getElementById('table-head-cell-' + col.id).offsetWidth)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
resizeCol (col, width) {
|
||||
if (!this.form) return
|
||||
const columns = clonedeep(this.form.properties)
|
||||
const index = this.form.properties.findIndex(c => c.id === col.id)
|
||||
columns[index].cell_width = width
|
||||
this.form.properties = columns
|
||||
this.$nextTick(() => {
|
||||
this.$emit('resize')
|
||||
})
|
||||
},
|
||||
handleScroll () {
|
||||
const parent = document.getElementById('table-page')
|
||||
const posTop = parent.getBoundingClientRect().top
|
||||
const tablePosition = Math.max(0, posTop - this.$refs.table.getBoundingClientRect().top)
|
||||
const tableHeader = document.getElementById('table-header-' + this.tableHash)
|
||||
|
||||
// Set position of table header
|
||||
if (tableHeader) {
|
||||
tableHeader.style.transform = `translate3d(0px, ${tablePosition}px, 0px)`
|
||||
if (tablePosition > 0) {
|
||||
tableHeader.classList.add('border-t')
|
||||
} else {
|
||||
tableHeader.classList.remove('border-t')
|
||||
}
|
||||
}
|
||||
|
||||
// Set position of actions row
|
||||
if (this.$slots.hasOwnProperty('actions')) {
|
||||
const tableActionsRow = document.getElementById('table-actions-' + this.tableHash)
|
||||
if (tableActionsRow) {
|
||||
if (tablePosition > 100) {
|
||||
tableActionsRow.style.transform = `translate3d(0px, ${tablePosition + 33}px, 0px)`
|
||||
} else {
|
||||
const parentContainer = document.getElementById('table-page')
|
||||
tableActionsRow.style.transform = `translate3d(0px, ${parentContainer.offsetHeight + (posTop - this.$refs.table.getBoundingClientRect().top) - 35}px, 0px)`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.n-table {
|
||||
.n-table-head {
|
||||
height: 33px;
|
||||
|
||||
.resize-handler {
|
||||
height: 33px;
|
||||
width: 5px;
|
||||
margin-left: -3px;
|
||||
}
|
||||
}
|
||||
|
||||
.n-table-row {
|
||||
display: flex;
|
||||
|
||||
&.first, &.loader {
|
||||
margin-top: 33px;
|
||||
}
|
||||
}
|
||||
|
||||
.n-table-cell {
|
||||
min-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.notion-table {
|
||||
|
||||
td {
|
||||
&.text-gray {
|
||||
color: #787774;
|
||||
}
|
||||
|
||||
&.text-brown {
|
||||
color: #9f6b53;
|
||||
}
|
||||
|
||||
&.text-orange {
|
||||
color: #d9730d;
|
||||
}
|
||||
|
||||
&.text-yellow {
|
||||
color: #cb912f;
|
||||
}
|
||||
|
||||
&.text-green {
|
||||
color: #448361;
|
||||
}
|
||||
|
||||
&.text-blue {
|
||||
color: #337ea9;
|
||||
}
|
||||
|
||||
&.text-purple {
|
||||
color: #9065b0;
|
||||
}
|
||||
|
||||
&.text-pink {
|
||||
color: #c14c8a;
|
||||
}
|
||||
|
||||
&.text-red {
|
||||
color: #d44c47;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.notion-table {
|
||||
td {
|
||||
&.text-gray {
|
||||
color: #9b9b9b;
|
||||
}
|
||||
|
||||
&.text-brown {
|
||||
color: #ba856f;
|
||||
}
|
||||
|
||||
&.text-orange {
|
||||
color: #c77d48;
|
||||
}
|
||||
|
||||
&.text-yellow {
|
||||
color: #ca9849;
|
||||
}
|
||||
|
||||
&.text-green {
|
||||
color: #529e72;
|
||||
}
|
||||
|
||||
&.text-blue {
|
||||
color: #5e87c9;
|
||||
}
|
||||
|
||||
&.text-purple {
|
||||
color: #9d68d3;
|
||||
}
|
||||
|
||||
&.text-pink {
|
||||
color: #d15796;
|
||||
}
|
||||
|
||||
&.text-red {
|
||||
color: #df5452;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
33
client/components/open/tables/components/OpenCheckbox.vue
Normal file
33
client/components/open/tables/components/OpenCheckbox.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<svg v-if="value===true" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mx-auto" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
<svg v-else-if="value===false" xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mx-auto" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
value: {
|
||||
required: true
|
||||
},
|
||||
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
computed: {
|
||||
},
|
||||
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
35
client/components/open/tables/components/OpenDate.vue
Normal file
35
client/components/open/tables/components/OpenDate.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<span v-if="valueIsObject">
|
||||
<template v-if="value[0]">{{ value[0] }}</template>
|
||||
<template v-if="value[1]"><b>to</b> {{ value[1] }}</template>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ value }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
value: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
valueIsObject () {
|
||||
if (typeof this.value === 'object' && this.value !== null) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
methods: {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
71
client/components/open/tables/components/OpenFile.vue
Normal file
71
client/components/open/tables/components/OpenFile.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<p class="text-xs">
|
||||
<span v-for="file in value" :key="file.file_url"
|
||||
class="whitespace-nowrap rounded-md transition-colors hover:decoration-none"
|
||||
:class="{'open-file text-gray-700 dark:text-gray-300 truncate':!isImage(file.file_url), 'open-file-img':isImage(file.file_url)}"
|
||||
>
|
||||
<a class="text-gray-700 dark:text-gray-300" :href="file.file_url" target="_blank"
|
||||
rel="nofollow"
|
||||
>
|
||||
<div v-if="isImage(file.file_url)" class="w-8 h-8">
|
||||
<img class="object-cover h-full w-full rounded" :src="file.file_url">
|
||||
</div>
|
||||
<span v-else
|
||||
class="py-1 px-2"
|
||||
>
|
||||
<a :href="file.file_url" target="_blank" download>{{ displayedFileName(file.file_name) }}</a>
|
||||
</span>
|
||||
</a>
|
||||
</span>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
value: {
|
||||
type: Array,
|
||||
required: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
computed: {},
|
||||
mounted() {
|
||||
},
|
||||
|
||||
methods: {
|
||||
isImage(url) {
|
||||
return ['png', 'gif', 'jpg', 'jpeg', 'tif'].some((suffix) => {
|
||||
return url && url.endsWith(suffix)
|
||||
})
|
||||
},
|
||||
displayedFileName(fileName) {
|
||||
const extension = fileName.substr(fileName.lastIndexOf(".") + 1)
|
||||
const filename = fileName.substr(0, fileName.lastIndexOf("."))
|
||||
|
||||
if (filename.length > 12) {
|
||||
return filename.substr(0, 12) + '(...).' + extension
|
||||
}
|
||||
return filename + '.' + extension
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.open-file {
|
||||
max-width: 120px;
|
||||
background-color: #e3e2e0;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.open-file {
|
||||
background-color: #5a5a5a;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
39
client/components/open/tables/components/OpenSelect.vue
Normal file
39
client/components/open/tables/components/OpenSelect.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<span v-if="value" class="-mb-2">
|
||||
<template v-if="valueIsObject">
|
||||
<open-tag v-for="(val,index) in value" :key="index+val" :opt="val" />
|
||||
</template>
|
||||
<open-tag v-else :opt="value" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import OpenTag from './OpenTag.vue'
|
||||
|
||||
export default {
|
||||
components: { OpenTag },
|
||||
props: {
|
||||
value: {
|
||||
type: Object | null,
|
||||
required: true
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
|
||||
computed: {
|
||||
valueIsObject () {
|
||||
if (typeof this.value === 'object' && this.value !== null) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
101
client/components/open/tables/components/OpenTag.vue
Normal file
101
client/components/open/tables/components/OpenTag.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<span :id="opt"
|
||||
class="py-1 px-2 mb-1 open-tag default mr-2 text-gray-700 dark:text-gray-300 text-xs whitespace-nowrap rounded-md transition-colors"
|
||||
>
|
||||
{{ opt }}
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
opt: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
|
||||
computed: {
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
|
||||
methods: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.open-tag {
|
||||
display: inline-block;
|
||||
&.gray {
|
||||
background-color: #e3e2e0;
|
||||
}
|
||||
&.light-gray,&.default {
|
||||
background-color: #e3e2e080;
|
||||
}
|
||||
&.brown {
|
||||
background-color: #eee0da;
|
||||
}
|
||||
&.orange {
|
||||
background-color: #fadec9;
|
||||
}
|
||||
&.yellow {
|
||||
background-color: #fdecc8;
|
||||
}
|
||||
&.green {
|
||||
background-color: #dbeddb;
|
||||
}
|
||||
&.blue {
|
||||
background-color: #d3e5ef;
|
||||
}
|
||||
&.purple {
|
||||
background-color: #e8deee;
|
||||
}
|
||||
&.pink {
|
||||
background-color: #f5e0e9;
|
||||
}
|
||||
&.red {
|
||||
background-color: #ffe2dd;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.open-tag {
|
||||
&.gray {
|
||||
background-color: #5a5a5a;
|
||||
}
|
||||
&.light-gray,&.default {
|
||||
background-color: #ffffff21;
|
||||
}
|
||||
&.brown {
|
||||
background-color: #603b2c;
|
||||
}
|
||||
&.orange {
|
||||
background-color: #854c1d;
|
||||
}
|
||||
&.yellow {
|
||||
background-color: #89632a;
|
||||
}
|
||||
&.green {
|
||||
background-color: #2b593f;
|
||||
}
|
||||
&.blue {
|
||||
background-color: #28456c;
|
||||
}
|
||||
&.purple {
|
||||
background-color: #492f64;
|
||||
}
|
||||
&.pink {
|
||||
background-color: #69314c;
|
||||
}
|
||||
&.red {
|
||||
background-color: #6e3630;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
99
client/components/open/tables/components/OpenText.vue
Normal file
99
client/components/open/tables/components/OpenText.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<span v-if="!valueIsObject">
|
||||
{{ value }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<span
|
||||
v-for="(item, i) in value.responseData"
|
||||
:key="i"
|
||||
:class="{
|
||||
'font-semibold': item.annotations.bold && !item.annotations.code,
|
||||
italic: item.annotations.italic,
|
||||
'line-through': item.annotations.strikethrough,
|
||||
underline: item.annotations.underline,
|
||||
'bg-pink-100 py-1 px-2 rounded-lg text-pink-500': item.annotations.code,
|
||||
'font-serif': item.type == 'equation',
|
||||
}"
|
||||
:style="{
|
||||
color:
|
||||
item.annotations.color != 'default'
|
||||
? getColor(item.annotations.color)
|
||||
: null,
|
||||
'background-color':
|
||||
item.annotations.color != 'default' &&
|
||||
item.annotations.color.split('_')[1]
|
||||
? getBgColor(item.annotations.color.split('_')[0])
|
||||
: 'none',
|
||||
}"
|
||||
>
|
||||
<a
|
||||
v-if="item.href"
|
||||
:href="item.href"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
class="text-blue-600 underline"
|
||||
>{{ item.plain_text }}</a>
|
||||
<span v-else-if="!item.href">{{ item.plain_text }}</span>
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
value: {
|
||||
required: true
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
data () {
|
||||
return {}
|
||||
},
|
||||
|
||||
computed: {
|
||||
valueIsObject () {
|
||||
if (
|
||||
typeof this.value === 'object' &&
|
||||
!Array.isArray(this.value) &&
|
||||
this.value !== null
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
|
||||
methods: {
|
||||
getColor (color) {
|
||||
return {
|
||||
red: '#e03e3e',
|
||||
gray: '#9b9a97',
|
||||
brown: '#64473a',
|
||||
orange: '#d9730d',
|
||||
yellow: '#dfab01',
|
||||
teal: '#0f7b6c',
|
||||
blue: '#0b6e99',
|
||||
purple: '#6940a5',
|
||||
pink: '#ad1a72'
|
||||
}[color]
|
||||
},
|
||||
getBgColor (color) {
|
||||
return {
|
||||
red: '#fbe4e4',
|
||||
gray: '#ebeced',
|
||||
brown: '#e9e5e3',
|
||||
orange: '#faebdd',
|
||||
yellow: '#fbf3db',
|
||||
teal: '#ddedea',
|
||||
blue: '#ddebf1',
|
||||
purple: '#eae4f2',
|
||||
pink: '#f4dfeb'
|
||||
}[color]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
26
client/components/open/tables/components/OpenUrl.vue
Normal file
26
client/components/open/tables/components/OpenUrl.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<a class="text-gray-700 dark:text-gray-300 hover:underline" :href="value" target="_blank" rel="nofollow">{{ value }}</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
value: {
|
||||
required: true
|
||||
},
|
||||
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
||||
computed: {
|
||||
},
|
||||
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
67
client/components/open/tables/components/ResizableTh.vue
Normal file
67
client/components/open/tables/components/ResizableTh.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<th ref="th" :style="{width: width}">
|
||||
<slot />
|
||||
<div v-if="allowResize" class="absolute right-0 top-0 w-0 z-10">
|
||||
<div class="resize-handler bg-transparent cursor-move hover:bg-blue-500 opacity-80 transition-colors"
|
||||
@mousedown="mouseDownHandler"
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
props: {
|
||||
allowResize: {
|
||||
required: true
|
||||
},
|
||||
width: {
|
||||
required: true
|
||||
}
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
x: 0,
|
||||
w: 0
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
|
||||
methods: {
|
||||
mouseDownHandler (e) {
|
||||
// Get the current mouse position
|
||||
this.x = e.clientX
|
||||
|
||||
// Calculate the dimension of element
|
||||
const styles = window.getComputedStyle(this.$refs.th)
|
||||
this.w = parseInt(styles.width, 10)
|
||||
|
||||
// Attach the listeners to `document`
|
||||
if (process.server) {
|
||||
document.addEventListener('mousemove', this.mouseMoveHandler)
|
||||
document.addEventListener('mouseup', this.mouseUpHandler)
|
||||
}
|
||||
},
|
||||
mouseMoveHandler (e) {
|
||||
// How far the mouse has been moved
|
||||
const dx = e.clientX - this.x
|
||||
|
||||
// Adjust the dimension of element
|
||||
this.$emit('resize-width', this.w + dx)
|
||||
},
|
||||
mouseUpHandler () {
|
||||
// Remove the handlers of `mousemove` and `mouseup`
|
||||
if (process.server) {
|
||||
document.removeEventListener('mousemove', this.mouseMoveHandler)
|
||||
document.removeEventListener('mouseup', this.mouseUpHandler)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user