@operato/attachment 10.22.0 → 10.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,30 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [10.24.0](https://github.com/hatiolab/operato/compare/v10.23.0...v10.24.0) (2026-09-24)
7
+
8
+ **Note:** Version bump only for package @operato/attachment
9
+
10
+
11
+
12
+
13
+
14
+ ## [10.23.0](https://github.com/hatiolab/operato/compare/v10.22.0...v10.23.0) (2026-09-24)
15
+
16
+
17
+ ### :rocket: New Features
18
+
19
+ * **attachment:** quiet cards for the attachment list; outlined card gutters ([97081d1](https://github.com/hatiolab/operato/commit/97081d17e37d99ae231317938329ccb52b4bfae6))
20
+ * **attachment:** tiles for files that are not pictures; one-line card titles ([371cafc](https://github.com/hatiolab/operato/commit/371cafc2b9745a2b15b7870f843767aff5646632))
21
+
22
+
23
+ ### :bug: Bug Fix
24
+
25
+ * **attachment:** card actions as five quiet icons, so the row fits any card width ([1eb0665](https://github.com/hatiolab/operato/commit/1eb066581aa215957d98a1a8e7851af650c159ca))
26
+ * **attachment:** draw the tile for files with no server thumbnail; tooltips on card fields ([4a0ee28](https://github.com/hatiolab/operato/commit/4a0ee28396564f098aca8f0b365be65921d24e07))
27
+
28
+
29
+
6
30
  ## [10.22.0](https://github.com/hatiolab/operato/compare/v10.21.0...v10.22.0) (2026-09-24)
7
31
 
8
32
  **Note:** Version bump only for package @operato/attachment
@@ -1 +1,2 @@
1
1
  export * from './ox-attachment-list.js';
2
+ export * from './ox-attachment-tile.js';
package/dist/src/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './ox-attachment-list.js';
2
+ export * from './ox-attachment-tile.js';
2
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA","sourcesContent":["export * from './ox-attachment-list.js'\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA","sourcesContent":["export * from './ox-attachment-list.js'\nexport * from './ox-attachment-tile.js'\n"]}
@@ -1,4 +1,5 @@
1
1
  import '@operato/data-grist';
2
+ import './ox-attachment-tile.js';
2
3
  import '@operato/input/ox-input-file.js';
3
4
  import { LitElement } from 'lit';
4
5
  import { ColumnConfig, DataGrist, FetchOption, FieldRenderer, FilterValue, GristData, GristRecord, SortersConfig } from '@operato/data-grist';
@@ -26,7 +27,6 @@ export declare class OxAttachmentList extends LitElement {
26
27
  icon?: undefined;
27
28
  title?: undefined;
28
29
  handlers?: undefined;
29
- iconOnly?: undefined;
30
30
  danger?: undefined;
31
31
  name?: undefined;
32
32
  hidden?: undefined;
@@ -34,7 +34,6 @@ export declare class OxAttachmentList extends LitElement {
34
34
  width?: undefined;
35
35
  filter?: undefined;
36
36
  sortable?: undefined;
37
- label?: undefined;
38
37
  record?: undefined;
39
38
  } | {
40
39
  type: string;
@@ -44,7 +43,6 @@ export declare class OxAttachmentList extends LitElement {
44
43
  handlers: {
45
44
  click: (columns: ColumnConfig[], data: GristData, column: ColumnConfig, record: GristRecord, rowIndex: number, target: HTMLElement) => void;
46
45
  };
47
- iconOnly?: undefined;
48
46
  danger?: undefined;
49
47
  name?: undefined;
50
48
  hidden?: undefined;
@@ -52,31 +50,11 @@ export declare class OxAttachmentList extends LitElement {
52
50
  width?: undefined;
53
51
  filter?: undefined;
54
52
  sortable?: undefined;
55
- label?: undefined;
56
53
  record?: undefined;
57
54
  } | {
58
55
  type: string;
59
56
  gutterName: string;
60
57
  icon: string;
61
- iconOnly: boolean;
62
- title: string;
63
- handlers: {
64
- click: (columns: ColumnConfig[], data: GristData, column: ColumnConfig, record: GristRecord, rowIndex: number, target: HTMLElement) => void;
65
- };
66
- danger?: undefined;
67
- name?: undefined;
68
- hidden?: undefined;
69
- header?: undefined;
70
- width?: undefined;
71
- filter?: undefined;
72
- sortable?: undefined;
73
- label?: undefined;
74
- record?: undefined;
75
- } | {
76
- type: string;
77
- gutterName: string;
78
- icon: string;
79
- iconOnly: boolean;
80
58
  danger: boolean;
81
59
  title: string;
82
60
  handlers: {
@@ -88,7 +66,6 @@ export declare class OxAttachmentList extends LitElement {
88
66
  width?: undefined;
89
67
  filter?: undefined;
90
68
  sortable?: undefined;
91
- label?: undefined;
92
69
  record?: undefined;
93
70
  } | {
94
71
  type: string;
@@ -98,13 +75,11 @@ export declare class OxAttachmentList extends LitElement {
98
75
  icon?: undefined;
99
76
  title?: undefined;
100
77
  handlers?: undefined;
101
- iconOnly?: undefined;
102
78
  danger?: undefined;
103
79
  header?: undefined;
104
80
  width?: undefined;
105
81
  filter?: undefined;
106
82
  sortable?: undefined;
107
- label?: undefined;
108
83
  record?: undefined;
109
84
  } | {
110
85
  type: string;
@@ -117,10 +92,8 @@ export declare class OxAttachmentList extends LitElement {
117
92
  icon?: undefined;
118
93
  title?: undefined;
119
94
  handlers?: undefined;
120
- iconOnly?: undefined;
121
95
  danger?: undefined;
122
96
  hidden?: undefined;
123
- label?: undefined;
124
97
  record?: undefined;
125
98
  } | {
126
99
  type: string;
@@ -132,11 +105,9 @@ export declare class OxAttachmentList extends LitElement {
132
105
  icon?: undefined;
133
106
  title?: undefined;
134
107
  handlers?: undefined;
135
- iconOnly?: undefined;
136
108
  danger?: undefined;
137
109
  hidden?: undefined;
138
110
  sortable?: undefined;
139
- label?: undefined;
140
111
  record?: undefined;
141
112
  } | {
142
113
  type: string;
@@ -153,19 +124,17 @@ export declare class OxAttachmentList extends LitElement {
153
124
  icon?: undefined;
154
125
  title?: undefined;
155
126
  handlers?: undefined;
156
- iconOnly?: undefined;
157
127
  danger?: undefined;
158
128
  width?: undefined;
159
129
  sortable?: undefined;
160
- label?: undefined;
161
130
  record?: undefined;
162
131
  } | {
163
132
  type: string;
164
133
  name: string;
165
134
  header: string;
166
- label: boolean;
167
135
  record: {
168
136
  editable: boolean;
137
+ renderEmpty?: undefined;
169
138
  renderer?: undefined;
170
139
  };
171
140
  filter: string;
@@ -174,7 +143,6 @@ export declare class OxAttachmentList extends LitElement {
174
143
  icon?: undefined;
175
144
  title?: undefined;
176
145
  handlers?: undefined;
177
- iconOnly?: undefined;
178
146
  danger?: undefined;
179
147
  hidden?: undefined;
180
148
  sortable?: undefined;
@@ -184,6 +152,7 @@ export declare class OxAttachmentList extends LitElement {
184
152
  hidden: boolean;
185
153
  record: {
186
154
  editable: boolean;
155
+ renderEmpty: boolean;
187
156
  renderer: FieldRenderer;
188
157
  };
189
158
  handlers: {
@@ -193,12 +162,10 @@ export declare class OxAttachmentList extends LitElement {
193
162
  gutterName?: undefined;
194
163
  icon?: undefined;
195
164
  title?: undefined;
196
- iconOnly?: undefined;
197
165
  danger?: undefined;
198
166
  header?: undefined;
199
167
  filter?: undefined;
200
168
  sortable?: undefined;
201
- label?: undefined;
202
169
  } | {
203
170
  type: string;
204
171
  name: string;
@@ -209,11 +176,9 @@ export declare class OxAttachmentList extends LitElement {
209
176
  icon?: undefined;
210
177
  title?: undefined;
211
178
  handlers?: undefined;
212
- iconOnly?: undefined;
213
179
  danger?: undefined;
214
180
  hidden?: undefined;
215
181
  filter?: undefined;
216
- label?: undefined;
217
182
  record?: undefined;
218
183
  })[];
219
184
  rows: {
@@ -1,5 +1,6 @@
1
1
  import { __decorate } from "tslib";
2
2
  import '@operato/data-grist';
3
+ import './ox-attachment-tile.js';
3
4
  import '@operato/input/ox-input-file.js';
4
5
  import gql from 'graphql-tag';
5
6
  import { css, html, LitElement } from 'lit';
@@ -161,7 +162,6 @@ let OxAttachmentList = class OxAttachmentList extends LitElement {
161
162
  type: 'gutter',
162
163
  gutterName: 'button',
163
164
  icon: 'save_alt',
164
- iconOnly: false,
165
165
  title: i18next.t('button.download'),
166
166
  handlers: {
167
167
  click: (columns, data, column, record, rowIndex, target) => {
@@ -178,7 +178,6 @@ let OxAttachmentList = class OxAttachmentList extends LitElement {
178
178
  type: 'gutter',
179
179
  gutterName: 'button',
180
180
  icon: 'delete',
181
- iconOnly: false,
182
181
  danger: true,
183
182
  title: i18next.t('button.delete'),
184
183
  handlers: {
@@ -224,7 +223,6 @@ let OxAttachmentList = class OxAttachmentList extends LitElement {
224
223
  type: 'hashtags',
225
224
  name: 'tags',
226
225
  header: i18next.t('field.hashtags'),
227
- label: true,
228
226
  record: {
229
227
  editable: false
230
228
  },
@@ -237,26 +235,25 @@ let OxAttachmentList = class OxAttachmentList extends LitElement {
237
235
  hidden: true,
238
236
  record: {
239
237
  editable: false,
238
+ /* A file with no server thumbnail still gets the slot: the renderer draws a tile for it. */
239
+ renderEmpty: true,
240
240
  renderer: function (value, column, record, rowIndex, owner) {
241
241
  // 1순위: 서버가 생성한 썸네일 (data URI 또는 외부 URL)
242
242
  // 이미지/GLTF 등 서버 측에서 썸네일이 있는 타입
243
243
  if (record.thumbnail) {
244
244
  return html ` <img src=${record.thumbnail} style="max-width: 100%; max-height: 100%;" /> `;
245
245
  }
246
- // 2순위: 비디오는 원본 컨트롤 재생
247
- if (record.category == 'video') {
248
- return html ` <video src=${record.fullpath} style="width: 100%; height: 100%;" controls></video> `;
249
- }
250
- // 3순위: 썸네일 없는 이미지는 원본으로 폴백 (기존 attachment 호환)
246
+ // 2순위: 썸네일 없는 이미지는 원본으로 폴백 (기존 attachment 호환)
251
247
  if (record.category == 'image') {
252
248
  return html ` <img src=${record.fullpath} style="max-width: 100%; max-height: 100%;" /> `;
253
249
  }
254
- // 4순위: 일반 파일 아이콘 + 확장자
250
+ // 3순위: 그림이 아닌 파일 - 첫 프레임 · 첫 줄 · 종류 글리프 (ox-attachment-tile)
255
251
  return html `
256
- <div style="width: 100%; height: 100%;" etc>
257
- <md-icon style="--md-icon-size: 24px;">insert_drive_file</md-icon>
258
- <span>${record.path.substr(record.path.lastIndexOf('.'))}</span>
259
- </div>
252
+ <ox-attachment-tile
253
+ .path=${record.path || record.name || ''}
254
+ .mimetype=${record.mimetype || ''}
255
+ .src=${record.fullpath || ''}
256
+ ></ox-attachment-tile>
260
257
  `;
261
258
  }
262
259
  },
@@ -408,6 +405,33 @@ OxAttachmentList.styles = [
408
405
 
409
406
  --grid-record-emphasized-background-color: red;
410
407
  --grid-record-emphasized-color: yellow;
408
+
409
+ /*
410
+ * The card: the file's picture on a plate across the top, then its name, when it changed,
411
+ * its tags if any, and the actions. One hairline, one radius, the same surfaces the
412
+ * product's other cards use; nothing coloured but the picture and the delete plate.
413
+ */
414
+ --data-card-min-width: 240px;
415
+ --data-card-record-card-background-color: var(--board-card-surface, var(--md-sys-color-surface-container-lowest));
416
+ --data-card-record-card-background-color-hover: var(--board-card-surface, var(--md-sys-color-surface-container-lowest));
417
+ --data-card-record-card-border: 1px solid var(--md-sys-color-outline-variant);
418
+ --data-card-record-card-border-hover: 1px solid var(--md-sys-color-outline);
419
+ --data-card-record-card-boxshadow-hover: 0 2px 10px rgba(0, 0, 0, 0.08);
420
+ --data-card-record-card-border-radius: 12px;
421
+ --data-card-record-card-padding: 0;
422
+ --data-card-thumbnail-height: 150px;
423
+ --data-card-thumbnail-padding: 12px;
424
+ --data-card-thumbnail-background: var(--board-card-plate, var(--md-sys-color-surface-container));
425
+ --data-card-thumbnail-radius: 11px 11px 0 0;
426
+ --data-card-item-margin: 12px 16px 0;
427
+ --data-card-item-name-font: 600 0.86rem/1.3 var(--theme-font, Roboto, sans-serif);
428
+ --data-card-item-name-lines: 1;
429
+ --ox-attachment-tile-fade: var(--board-card-plate, var(--md-sys-color-surface-container));
430
+ --data-card-item-name-color: var(--md-sys-color-on-surface);
431
+ --data-card-item-etc-font: 0.74rem/1.3 var(--theme-font, Roboto, sans-serif);
432
+ --data-card-item-etc-color: var(--md-sys-color-on-surface-variant);
433
+ --data-card-detail-border-top: none;
434
+ --data-card-gutters-margin: 8px 12px 12px;
411
435
  }
412
436
 
413
437
  .header .filters {
@@ -1 +1 @@
1
- {"version":3,"file":"ox-attachment-list.js","sourceRoot":"","sources":["../../src/ox-attachment-list.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist'\nimport '@operato/input/ox-input-file.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { openPopup, PopupHandle } from '@operato/popup'\n\nimport {\n ColumnConfig,\n DataGrist,\n FetchOption,\n FieldRenderer,\n FilterValue,\n GristData,\n GristRecord,\n PaginationConfig,\n SortersConfig\n} from '@operato/data-grist'\nimport { buildArgs, client } from '@operato/graphql'\nimport { ScrollbarStyles, CommonGristStyles, CommonHeaderStyles } from '@operato/styles'\nimport { i18next } from '@operato/i18n'\nimport { copyToClipboard, sleep } from '@operato/utils'\n\nconst FETCH_ATTACHMENT_LIST_GQL = (listParam: any) => {\n return gql`\n {\n attachments(${buildArgs(listParam)}) {\n items {\n id\n name\n description\n mimetype\n encoding\n category\n fullpath\n path\n thumbnail\n tags\n createdAt\n updatedAt\n }\n total\n }\n }\n`\n}\n\nconst DELETE_ATTACHMENT_GQL = gql`\n mutation DeleteAttachment($id: String!) {\n deleteAttachment(id: $id)\n }\n`\n\nconst CREATE_ATTACHMENTS_GQL = gql`\n mutation ($attachments: [NewAttachment!]!) {\n createAttachments(attachments: $attachments) {\n id\n name\n description\n mimetype\n encoding\n category\n path\n tags\n createdAt\n updatedAt\n }\n }\n`\n\nconst UPDATE_ATTACHMENT_GQL = gql`\n mutation UpdateAttachment($patch: AttachmentPatch!, $id: String!) {\n updateAttachment(patch: $patch, id: $id) {\n id\n name\n description\n mimetype\n encoding\n category\n path\n tags\n createdAt\n updatedAt\n }\n }\n`\n\n@customElement('ox-attachment-list')\nexport class OxAttachmentList extends LitElement {\n static styles = [\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n\n width: 100%;\n\n --grid-record-emphasized-background-color: red;\n --grid-record-emphasized-color: yellow;\n }\n\n ox-grist {\n flex: 1;\n overflow-y: auto;\n\n --grid-record-emphasized-background-color: red;\n --grid-record-emphasized-color: yellow;\n }\n\n .header .filters {\n flex-direction: column;\n align-items: stretch;\n }\n `\n ]\n\n @property({ type: String }) category: string = ''\n @property({ type: Boolean }) creatable: boolean = false\n @property({ type: Boolean, attribute: 'without-search' }) withoutSearch: boolean = false\n\n @query('ox-grist') grist!: DataGrist\n @query('ox-input-file') fileUploader!: any\n\n render() {\n return html`\n <ox-grist .config=${this.gristConfig} .mode=${'CARD'} auto-fetch .fetchHandler=${this.fetchHandler.bind(this)}>\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <ox-input-file\n accept=\"*/*\"\n multiple=\"true\"\n hide-filelist\n @change=${this.onCreateAttachment.bind(this)}\n ></ox-input-file>\n\n <ox-filters-form autofocus .withoutSearch=${this.withoutSearch}></ox-filters-form>\n </div>\n </div>\n </ox-grist>\n `\n }\n\n async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {\n const { items: records, total } = await this.getAttachments({ page, limit, filters, sortings })\n\n return {\n total,\n records\n }\n }\n\n get gristConfig() {\n return {\n list: {\n thumbnail: 'thumbnail',\n fields: ['name'],\n details: ['tags', 'updatedAt']\n },\n columns: [\n {\n type: 'gutter',\n gutterName: 'dirty'\n },\n {\n type: 'gutter',\n gutterName: 'sequence'\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'tag',\n title: i18next.t('field.hashtags'),\n handlers: {\n click: (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): void => {\n this.popupHashtagEditor(record)\n }\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'open_in_new',\n title: i18next.t('button.open-in-new'),\n handlers: {\n click: (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): void => {\n window.open(record.fullpath, '_blank')\n }\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'link',\n title: i18next.t('button.copy-url'),\n handlers: {\n click: async (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): Promise<void> => {\n var { protocol, hostname, port } = location\n await copyToClipboard(`${protocol}//${hostname}:${port}${record.fullpath}`)\n\n target.setAttribute('data-tooltip', 'url copied!')\n\n const rect = target.getBoundingClientRect()\n target.style.setProperty('--tooltip-top', `${rect.top}px`)\n target.style.setProperty('--tooltip-left', `${rect.left}px`)\n\n await sleep(2000)\n target.removeAttribute('data-tooltip')\n }\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'save_alt',\n iconOnly: false,\n title: i18next.t('button.download'),\n handlers: {\n click: (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): void => {\n const element = document.createElement('a')\n element.setAttribute('href', record.fullpath)\n element.setAttribute('download', record.name!)\n document.body.appendChild(element)\n element.click()\n element.remove()\n }\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'delete',\n iconOnly: false,\n danger: true,\n title: i18next.t('button.delete'),\n handlers: {\n click: async (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): Promise<void> => {\n await this.deleteAttachment(record.id!)\n this.refreshAttachments()\n }\n }\n },\n {\n type: 'string',\n name: 'id',\n hidden: true\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n width: 200,\n filter: 'search',\n sortable: true\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n width: 200,\n filter: 'search'\n },\n {\n type: 'select-buttons',\n name: 'category',\n header: i18next.t('field.category'),\n hidden: true,\n filter: {\n operator: 'in',\n options: ['audio', 'video', 'image', 'text', 'application'],\n value: this.category,\n label: ''\n }\n },\n {\n type: 'hashtags',\n name: 'tags',\n header: i18next.t('field.hashtags'),\n label: true,\n record: {\n editable: false\n },\n filter: 'search',\n width: 200\n },\n {\n type: 'image',\n name: 'thumbnail',\n hidden: true,\n record: {\n editable: false,\n renderer: function (value, column, record, rowIndex, owner) {\n // 1순위: 서버가 생성한 썸네일 (data URI 또는 외부 URL)\n // 이미지/GLTF 등 서버 측에서 썸네일이 있는 타입\n if (record.thumbnail) {\n return html` <img src=${record.thumbnail} style=\"max-width: 100%; max-height: 100%;\" /> `\n }\n // 2순위: 비디오는 원본 컨트롤 재생\n if (record.category == 'video') {\n return html` <video src=${record.fullpath} style=\"width: 100%; height: 100%;\" controls></video> `\n }\n // 3순위: 썸네일 없는 이미지는 원본으로 폴백 (기존 attachment 호환)\n if (record.category == 'image') {\n return html` <img src=${record.fullpath} style=\"max-width: 100%; max-height: 100%;\" /> `\n }\n // 4순위: 일반 파일 아이콘 + 확장자\n return html`\n <div style=\"width: 100%; height: 100%;\" etc>\n <md-icon style=\"--md-icon-size: 24px;\">insert_drive_file</md-icon>\n <span>${record.path.substr(record.path.lastIndexOf('.'))}</span>\n </div>\n `\n } as FieldRenderer\n },\n handlers: {\n click: (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): void => {\n this.onClickSelect(record)\n }\n },\n width: 120\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated-at'),\n sortable: true,\n width: 180\n },\n {\n type: 'datetime',\n name: 'createdAt',\n header: i18next.t('field.created-at'),\n sortable: true,\n width: 180\n }\n ],\n rows: {\n appendable: false,\n selectable: {\n multiple: true\n },\n classifier: function (\n record: GristRecord,\n rowIndex: number\n ): { emphasized?: boolean | string | string[]; [key: string]: any } | void {}\n },\n sorters: [\n {\n name: 'name',\n desc: false\n }\n ],\n pagination: {\n pages: [20, 30, 50, 100, 200]\n }\n }\n }\n\n async firstUpdated() {\n this.refreshAttachments()\n }\n\n onClickSelect(attachment: any) {\n this.dispatchEvent(\n new CustomEvent('attachment-selected', {\n composed: true,\n bubbles: true,\n detail: {\n attachment\n }\n })\n )\n }\n\n async onCreateAttachment(e: CustomEvent) {\n const files = e.detail\n\n await this.createAttachments(files)\n this.refreshAttachments()\n }\n\n async onDeleteAttachment(id: string) {\n await this.deleteAttachment(id)\n\n this.refreshAttachments()\n }\n\n async refreshAttachments() {\n this.grist.fetch()\n }\n\n async getAttachments({\n page = 1,\n limit = 30,\n filters = [],\n sortings = []\n }: { page?: number; limit?: number; filters?: FilterValue[]; sortings?: SortersConfig } = {}) {\n var pagination: PaginationConfig = {\n limit,\n page\n }\n\n var params = {\n filters,\n sortings,\n pagination\n }\n\n var attachmentListResponse = await client.query({\n query: FETCH_ATTACHMENT_LIST_GQL(params)\n })\n\n return attachmentListResponse?.data?.attachments || {}\n }\n\n async createAttachments(files: File[]) {\n /*\n ref. https://github.com/jaydenseric/graphql-multipart-request-spec#client\n */\n\n const response = await client.mutate({\n mutation: CREATE_ATTACHMENTS_GQL,\n variables: {\n attachments: files.map(file => {\n return { file }\n })\n },\n context: {\n hasUpload: true\n }\n })\n }\n\n async updateAttachment(patch: any) {\n const response = await client.mutate({\n mutation: UPDATE_ATTACHMENT_GQL,\n variables: patch\n })\n }\n\n async deleteAttachment(id: string) {\n const response = await client.mutate({\n mutation: DELETE_ATTACHMENT_GQL,\n variables: {\n id\n }\n })\n\n return response.data\n }\n\n popupHashtagEditor(record: GristRecord) {\n const hashtags = Array.isArray(record.tags) ? [...record.tags] : []\n\n const confirmCallback = async (newval: any) => {\n await this.updateAttachment({\n id: record.id,\n patch: { tags: newval }\n })\n\n this.refreshAttachments()\n }\n\n openPopup(\n html`\n <ox-popup-hashtags-input .value=${hashtags} .confirmCallback=${confirmCallback}></ox-popup-hashtags-input>\n `,\n {\n backdrop: true,\n title: i18next.t('title.edit hashtags')\n }\n )\n }\n}\n"]}
1
+ {"version":3,"file":"ox-attachment-list.js","sourceRoot":"","sources":["../../src/ox-attachment-list.ts"],"names":[],"mappings":"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","sourcesContent":["import '@operato/data-grist'\nimport './ox-attachment-tile.js'\nimport '@operato/input/ox-input-file.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, query } from 'lit/decorators.js'\nimport { openPopup, PopupHandle } from '@operato/popup'\n\nimport {\n ColumnConfig,\n DataGrist,\n FetchOption,\n FieldRenderer,\n FilterValue,\n GristData,\n GristRecord,\n PaginationConfig,\n SortersConfig\n} from '@operato/data-grist'\nimport { buildArgs, client } from '@operato/graphql'\nimport { ScrollbarStyles, CommonGristStyles, CommonHeaderStyles } from '@operato/styles'\nimport { i18next } from '@operato/i18n'\nimport { copyToClipboard, sleep } from '@operato/utils'\n\nconst FETCH_ATTACHMENT_LIST_GQL = (listParam: any) => {\n return gql`\n {\n attachments(${buildArgs(listParam)}) {\n items {\n id\n name\n description\n mimetype\n encoding\n category\n fullpath\n path\n thumbnail\n tags\n createdAt\n updatedAt\n }\n total\n }\n }\n`\n}\n\nconst DELETE_ATTACHMENT_GQL = gql`\n mutation DeleteAttachment($id: String!) {\n deleteAttachment(id: $id)\n }\n`\n\nconst CREATE_ATTACHMENTS_GQL = gql`\n mutation ($attachments: [NewAttachment!]!) {\n createAttachments(attachments: $attachments) {\n id\n name\n description\n mimetype\n encoding\n category\n path\n tags\n createdAt\n updatedAt\n }\n }\n`\n\nconst UPDATE_ATTACHMENT_GQL = gql`\n mutation UpdateAttachment($patch: AttachmentPatch!, $id: String!) {\n updateAttachment(patch: $patch, id: $id) {\n id\n name\n description\n mimetype\n encoding\n category\n path\n tags\n createdAt\n updatedAt\n }\n }\n`\n\n@customElement('ox-attachment-list')\nexport class OxAttachmentList extends LitElement {\n static styles = [\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n\n width: 100%;\n\n --grid-record-emphasized-background-color: red;\n --grid-record-emphasized-color: yellow;\n }\n\n ox-grist {\n flex: 1;\n overflow-y: auto;\n\n --grid-record-emphasized-background-color: red;\n --grid-record-emphasized-color: yellow;\n\n /*\n * The card: the file's picture on a plate across the top, then its name, when it changed,\n * its tags if any, and the actions. One hairline, one radius, the same surfaces the\n * product's other cards use; nothing coloured but the picture and the delete plate.\n */\n --data-card-min-width: 240px;\n --data-card-record-card-background-color: var(--board-card-surface, var(--md-sys-color-surface-container-lowest));\n --data-card-record-card-background-color-hover: var(--board-card-surface, var(--md-sys-color-surface-container-lowest));\n --data-card-record-card-border: 1px solid var(--md-sys-color-outline-variant);\n --data-card-record-card-border-hover: 1px solid var(--md-sys-color-outline);\n --data-card-record-card-boxshadow-hover: 0 2px 10px rgba(0, 0, 0, 0.08);\n --data-card-record-card-border-radius: 12px;\n --data-card-record-card-padding: 0;\n --data-card-thumbnail-height: 150px;\n --data-card-thumbnail-padding: 12px;\n --data-card-thumbnail-background: var(--board-card-plate, var(--md-sys-color-surface-container));\n --data-card-thumbnail-radius: 11px 11px 0 0;\n --data-card-item-margin: 12px 16px 0;\n --data-card-item-name-font: 600 0.86rem/1.3 var(--theme-font, Roboto, sans-serif);\n --data-card-item-name-lines: 1;\n --ox-attachment-tile-fade: var(--board-card-plate, var(--md-sys-color-surface-container));\n --data-card-item-name-color: var(--md-sys-color-on-surface);\n --data-card-item-etc-font: 0.74rem/1.3 var(--theme-font, Roboto, sans-serif);\n --data-card-item-etc-color: var(--md-sys-color-on-surface-variant);\n --data-card-detail-border-top: none;\n --data-card-gutters-margin: 8px 12px 12px;\n }\n\n .header .filters {\n flex-direction: column;\n align-items: stretch;\n }\n `\n ]\n\n @property({ type: String }) category: string = ''\n @property({ type: Boolean }) creatable: boolean = false\n @property({ type: Boolean, attribute: 'without-search' }) withoutSearch: boolean = false\n\n @query('ox-grist') grist!: DataGrist\n @query('ox-input-file') fileUploader!: any\n\n render() {\n return html`\n <ox-grist .config=${this.gristConfig} .mode=${'CARD'} auto-fetch .fetchHandler=${this.fetchHandler.bind(this)}>\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <ox-input-file\n accept=\"*/*\"\n multiple=\"true\"\n hide-filelist\n @change=${this.onCreateAttachment.bind(this)}\n ></ox-input-file>\n\n <ox-filters-form autofocus .withoutSearch=${this.withoutSearch}></ox-filters-form>\n </div>\n </div>\n </ox-grist>\n `\n }\n\n async fetchHandler({ page = 1, limit = 100, sortings = [], filters = [] }: FetchOption) {\n const { items: records, total } = await this.getAttachments({ page, limit, filters, sortings })\n\n return {\n total,\n records\n }\n }\n\n get gristConfig() {\n return {\n list: {\n thumbnail: 'thumbnail',\n fields: ['name'],\n details: ['tags', 'updatedAt']\n },\n columns: [\n {\n type: 'gutter',\n gutterName: 'dirty'\n },\n {\n type: 'gutter',\n gutterName: 'sequence'\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'tag',\n title: i18next.t('field.hashtags'),\n handlers: {\n click: (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): void => {\n this.popupHashtagEditor(record)\n }\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'open_in_new',\n title: i18next.t('button.open-in-new'),\n handlers: {\n click: (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): void => {\n window.open(record.fullpath, '_blank')\n }\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'link',\n title: i18next.t('button.copy-url'),\n handlers: {\n click: async (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): Promise<void> => {\n var { protocol, hostname, port } = location\n await copyToClipboard(`${protocol}//${hostname}:${port}${record.fullpath}`)\n\n target.setAttribute('data-tooltip', 'url copied!')\n\n const rect = target.getBoundingClientRect()\n target.style.setProperty('--tooltip-top', `${rect.top}px`)\n target.style.setProperty('--tooltip-left', `${rect.left}px`)\n\n await sleep(2000)\n target.removeAttribute('data-tooltip')\n }\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'save_alt',\n title: i18next.t('button.download'),\n handlers: {\n click: (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): void => {\n const element = document.createElement('a')\n element.setAttribute('href', record.fullpath)\n element.setAttribute('download', record.name!)\n document.body.appendChild(element)\n element.click()\n element.remove()\n }\n }\n },\n {\n type: 'gutter',\n gutterName: 'button',\n icon: 'delete',\n danger: true,\n title: i18next.t('button.delete'),\n handlers: {\n click: async (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): Promise<void> => {\n await this.deleteAttachment(record.id!)\n this.refreshAttachments()\n }\n }\n },\n {\n type: 'string',\n name: 'id',\n hidden: true\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n width: 200,\n filter: 'search',\n sortable: true\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n width: 200,\n filter: 'search'\n },\n {\n type: 'select-buttons',\n name: 'category',\n header: i18next.t('field.category'),\n hidden: true,\n filter: {\n operator: 'in',\n options: ['audio', 'video', 'image', 'text', 'application'],\n value: this.category,\n label: ''\n }\n },\n {\n type: 'hashtags',\n name: 'tags',\n header: i18next.t('field.hashtags'),\n record: {\n editable: false\n },\n filter: 'search',\n width: 200\n },\n {\n type: 'image',\n name: 'thumbnail',\n hidden: true,\n record: {\n editable: false,\n /* A file with no server thumbnail still gets the slot: the renderer draws a tile for it. */\n renderEmpty: true,\n renderer: function (value, column, record, rowIndex, owner) {\n // 1순위: 서버가 생성한 썸네일 (data URI 또는 외부 URL)\n // 이미지/GLTF 등 서버 측에서 썸네일이 있는 타입\n if (record.thumbnail) {\n return html` <img src=${record.thumbnail} style=\"max-width: 100%; max-height: 100%;\" /> `\n }\n // 2순위: 썸네일 없는 이미지는 원본으로 폴백 (기존 attachment 호환)\n if (record.category == 'image') {\n return html` <img src=${record.fullpath} style=\"max-width: 100%; max-height: 100%;\" /> `\n }\n // 3순위: 그림이 아닌 파일 - 첫 프레임 · 첫 줄 · 종류 글리프 (ox-attachment-tile)\n return html`\n <ox-attachment-tile\n .path=${record.path || record.name || ''}\n .mimetype=${record.mimetype || ''}\n .src=${record.fullpath || ''}\n ></ox-attachment-tile>\n `\n } as FieldRenderer\n },\n handlers: {\n click: (\n columns: ColumnConfig[],\n data: GristData,\n column: ColumnConfig,\n record: GristRecord,\n rowIndex: number,\n target: HTMLElement\n ): void => {\n this.onClickSelect(record)\n }\n },\n width: 120\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated-at'),\n sortable: true,\n width: 180\n },\n {\n type: 'datetime',\n name: 'createdAt',\n header: i18next.t('field.created-at'),\n sortable: true,\n width: 180\n }\n ],\n rows: {\n appendable: false,\n selectable: {\n multiple: true\n },\n classifier: function (\n record: GristRecord,\n rowIndex: number\n ): { emphasized?: boolean | string | string[]; [key: string]: any } | void {}\n },\n sorters: [\n {\n name: 'name',\n desc: false\n }\n ],\n pagination: {\n pages: [20, 30, 50, 100, 200]\n }\n }\n }\n\n async firstUpdated() {\n this.refreshAttachments()\n }\n\n onClickSelect(attachment: any) {\n this.dispatchEvent(\n new CustomEvent('attachment-selected', {\n composed: true,\n bubbles: true,\n detail: {\n attachment\n }\n })\n )\n }\n\n async onCreateAttachment(e: CustomEvent) {\n const files = e.detail\n\n await this.createAttachments(files)\n this.refreshAttachments()\n }\n\n async onDeleteAttachment(id: string) {\n await this.deleteAttachment(id)\n\n this.refreshAttachments()\n }\n\n async refreshAttachments() {\n this.grist.fetch()\n }\n\n async getAttachments({\n page = 1,\n limit = 30,\n filters = [],\n sortings = []\n }: { page?: number; limit?: number; filters?: FilterValue[]; sortings?: SortersConfig } = {}) {\n var pagination: PaginationConfig = {\n limit,\n page\n }\n\n var params = {\n filters,\n sortings,\n pagination\n }\n\n var attachmentListResponse = await client.query({\n query: FETCH_ATTACHMENT_LIST_GQL(params)\n })\n\n return attachmentListResponse?.data?.attachments || {}\n }\n\n async createAttachments(files: File[]) {\n /*\n ref. https://github.com/jaydenseric/graphql-multipart-request-spec#client\n */\n\n const response = await client.mutate({\n mutation: CREATE_ATTACHMENTS_GQL,\n variables: {\n attachments: files.map(file => {\n return { file }\n })\n },\n context: {\n hasUpload: true\n }\n })\n }\n\n async updateAttachment(patch: any) {\n const response = await client.mutate({\n mutation: UPDATE_ATTACHMENT_GQL,\n variables: patch\n })\n }\n\n async deleteAttachment(id: string) {\n const response = await client.mutate({\n mutation: DELETE_ATTACHMENT_GQL,\n variables: {\n id\n }\n })\n\n return response.data\n }\n\n popupHashtagEditor(record: GristRecord) {\n const hashtags = Array.isArray(record.tags) ? [...record.tags] : []\n\n const confirmCallback = async (newval: any) => {\n await this.updateAttachment({\n id: record.id,\n patch: { tags: newval }\n })\n\n this.refreshAttachments()\n }\n\n openPopup(\n html`\n <ox-popup-hashtags-input .value=${hashtags} .confirmCallback=${confirmCallback}></ox-popup-hashtags-input>\n `,\n {\n backdrop: true,\n title: i18next.t('title.edit hashtags')\n }\n )\n }\n}\n"]}
@@ -10,7 +10,7 @@ export declare class OxAttachmentSelector extends LitElement {
10
10
  properties: any;
11
11
  private popup?;
12
12
  render(): import("lit-html").TemplateResult<1>;
13
- getIconByCategory(): "image" | "library_music" | "video_library" | "attachment";
13
+ getIconByCategory(): "library_music" | "video_library" | "image" | "attachment";
14
14
  _onInputChanged(e: KeyboardEvent): void;
15
15
  openSelector(): void;
16
16
  }
@@ -0,0 +1,14 @@
1
+ import '@material/web/icon/icon.js';
2
+ import { LitElement } from 'lit';
3
+ export declare function fileFamilyIcon(mimetype?: string, path?: string): string;
4
+ export declare class OxAttachmentTile extends LitElement {
5
+ static styles: import("lit").CSSResult[];
6
+ path: string;
7
+ mimetype: string;
8
+ src: string;
9
+ private _snippet?;
10
+ private _snippetFor?;
11
+ render(): import("lit-html").TemplateResult<1>;
12
+ updated(): void;
13
+ private _loadSnippet;
14
+ }
@@ -0,0 +1,194 @@
1
+ import { __decorate } from "tslib";
2
+ import '@material/web/icon/icon.js';
3
+ import { css, html, LitElement, nothing } from 'lit';
4
+ import { customElement, property, state } from 'lit/decorators.js';
5
+ /*
6
+ * What stands in for a picture when the file is not one.
7
+ *
8
+ * The server thumbnails images and glTF models. Everything else used to get the same grey
9
+ * "insert_drive_file" glyph, so a list of audio, text and zip files was a wall of identical
10
+ * tiles. This tile draws what it can from the file itself, cheaply:
11
+ *
12
+ * - video: the file's own first frame (the browser decodes just the metadata and one frame)
13
+ * - text (txt, csv, json, md, code): the first few lines, fetched with a byte range
14
+ * - everything else: a glyph for the file's family, with its extension
15
+ *
16
+ * A glyph tile is never blank - the family and the extension are the two things a person
17
+ * scans a file list for.
18
+ */
19
+ const TEXT_PREVIEW_BYTES = 600;
20
+ const FAMILIES = [
21
+ { test: mime => mime === 'application/pdf', icon: 'picture_as_pdf' },
22
+ { test: mime => mime.startsWith('audio/'), icon: 'audio_file' },
23
+ { test: mime => mime.startsWith('video/'), icon: 'video_file' },
24
+ { test: mime => mime.startsWith('model/'), icon: 'view_in_ar' },
25
+ { test: mime => mime.startsWith('font/'), icon: 'font_download' },
26
+ {
27
+ test: (mime, ext) => /zip|compressed|tar|gzip|rar|7z/.test(mime) || ['zip', 'tar', 'gz', 'rar', '7z'].includes(ext),
28
+ icon: 'folder_zip'
29
+ },
30
+ {
31
+ test: (mime, ext) => /spreadsheet|excel|csv/.test(mime) || ['xls', 'xlsx', 'csv', 'numbers'].includes(ext),
32
+ icon: 'table_chart'
33
+ },
34
+ {
35
+ test: (mime, ext) => /presentation|powerpoint/.test(mime) || ['ppt', 'pptx', 'key'].includes(ext),
36
+ icon: 'slideshow'
37
+ },
38
+ {
39
+ test: (mime, ext) => /msword|wordprocessing|rtf/.test(mime) || ['doc', 'docx', 'pages'].includes(ext),
40
+ icon: 'description'
41
+ },
42
+ {
43
+ test: (mime, ext) => /json|xml|javascript|typescript|yaml|x-sh/.test(mime) ||
44
+ ['js', 'ts', 'json', 'xml', 'yaml', 'yml', 'sh', 'py', 'java', 'css', 'html'].includes(ext),
45
+ icon: 'code'
46
+ },
47
+ { test: mime => mime.startsWith('text/'), icon: 'article' }
48
+ ];
49
+ function extensionOf(path = '') {
50
+ const dot = path.lastIndexOf('.');
51
+ return dot >= 0 ? path.slice(dot + 1).toLowerCase() : '';
52
+ }
53
+ export function fileFamilyIcon(mimetype = '', path = '') {
54
+ var _a;
55
+ const ext = extensionOf(path);
56
+ return ((_a = FAMILIES.find(family => family.test(mimetype, ext))) === null || _a === void 0 ? void 0 : _a.icon) || 'draft';
57
+ }
58
+ function isTextLike(mimetype = '', ext) {
59
+ return (mimetype.startsWith('text/') ||
60
+ /json|xml|javascript|yaml|x-sh/.test(mimetype) ||
61
+ ['txt', 'md', 'csv', 'json', 'xml', 'yaml', 'yml', 'log', 'js', 'ts', 'py', 'sh', 'css', 'html', 'sql'].includes(ext));
62
+ }
63
+ let OxAttachmentTile = class OxAttachmentTile extends LitElement {
64
+ constructor() {
65
+ super(...arguments);
66
+ this.path = '';
67
+ this.mimetype = '';
68
+ this.src = '';
69
+ }
70
+ render() {
71
+ const ext = extensionOf(this.path);
72
+ const mime = this.mimetype || '';
73
+ if (mime.startsWith('video/') && this.src) {
74
+ return html `<video src="${this.src}#t=0.5" preload="metadata" muted playsinline></video>`;
75
+ }
76
+ if (this._snippet) {
77
+ return html `<pre text>${this._snippet}</pre>`;
78
+ }
79
+ return html `
80
+ <div glyph>
81
+ <md-icon>${fileFamilyIcon(mime, this.path)}</md-icon>
82
+ ${ext ? html `<span ext>${ext}</span>` : nothing}
83
+ </div>
84
+ `;
85
+ }
86
+ updated() {
87
+ const ext = extensionOf(this.path);
88
+ if (this.src && isTextLike(this.mimetype, ext) && this._snippetFor !== this.src) {
89
+ this._snippetFor = this.src;
90
+ this._loadSnippet(this.src);
91
+ }
92
+ }
93
+ async _loadSnippet(src) {
94
+ try {
95
+ const response = await fetch(src, { headers: { Range: `bytes=0-${TEXT_PREVIEW_BYTES - 1}` } });
96
+ if (!response.ok)
97
+ return;
98
+ const text = (await response.text()).slice(0, TEXT_PREVIEW_BYTES);
99
+ /* A file that starts with binary or is empty gets the glyph instead. */
100
+ if (!text.trim() || /[\u0000-\u0008\u000e-\u001f]/.test(text))
101
+ return;
102
+ if (this._snippetFor === src)
103
+ this._snippet = text;
104
+ }
105
+ catch (e) {
106
+ /* the glyph stays */
107
+ }
108
+ }
109
+ };
110
+ OxAttachmentTile.styles = [
111
+ css `
112
+ :host {
113
+ display: flex;
114
+ width: 100%;
115
+ height: 100%;
116
+ min-height: 0;
117
+ box-sizing: border-box;
118
+ overflow: hidden;
119
+ }
120
+
121
+ [glyph] {
122
+ flex: 1;
123
+ display: flex;
124
+ flex-direction: column;
125
+ align-items: center;
126
+ justify-content: center;
127
+ gap: 6px;
128
+ color: var(--md-sys-color-on-surface-variant);
129
+ }
130
+
131
+ [glyph] md-icon {
132
+ --md-icon-size: 40px;
133
+ font-variation-settings: 'wght' 300;
134
+ color: var(--md-sys-color-outline);
135
+ }
136
+
137
+ [ext] {
138
+ padding: 0 8px;
139
+ border-radius: 999px;
140
+ font-size: 0.66rem;
141
+ font-weight: 600;
142
+ letter-spacing: 0.06em;
143
+ line-height: 18px;
144
+ text-transform: uppercase;
145
+ color: var(--md-sys-color-on-secondary-container);
146
+ background: var(--md-sys-color-secondary-container);
147
+ }
148
+
149
+ video {
150
+ width: 100%;
151
+ height: 100%;
152
+ object-fit: contain;
153
+ pointer-events: none;
154
+ }
155
+
156
+ /* The first lines of a text file, as they are, in a small monospace; the fade says "more". */
157
+ [text] {
158
+ position: relative;
159
+ flex: 1;
160
+ margin: 0;
161
+ padding: 10px 12px;
162
+ overflow: hidden;
163
+ font: 0.66rem/1.45 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
164
+ color: var(--md-sys-color-on-surface-variant);
165
+ white-space: pre-wrap;
166
+ word-break: break-all;
167
+ }
168
+
169
+ [text]::after {
170
+ content: '';
171
+ position: absolute;
172
+ inset: auto 0 0 0;
173
+ height: 40%;
174
+ background: linear-gradient(to bottom, transparent, var(--ox-attachment-tile-fade, var(--md-sys-color-surface-container)));
175
+ }
176
+ `
177
+ ];
178
+ __decorate([
179
+ property({ type: String })
180
+ ], OxAttachmentTile.prototype, "path", void 0);
181
+ __decorate([
182
+ property({ type: String })
183
+ ], OxAttachmentTile.prototype, "mimetype", void 0);
184
+ __decorate([
185
+ property({ type: String })
186
+ ], OxAttachmentTile.prototype, "src", void 0);
187
+ __decorate([
188
+ state()
189
+ ], OxAttachmentTile.prototype, "_snippet", void 0);
190
+ OxAttachmentTile = __decorate([
191
+ customElement('ox-attachment-tile')
192
+ ], OxAttachmentTile);
193
+ export { OxAttachmentTile };
194
+ //# sourceMappingURL=ox-attachment-tile.js.map