diff --git a/lib/app/communications/contact.js b/lib/app/communications/contact.js
index eb0e73a..5766087 100644
--- a/lib/app/communications/contact.js
+++ b/lib/app/communications/contact.js
@@ -1,7 +1,7 @@
 import { Grid, Dropdown, createElement, createCheckbox, Popup, showAlert } from "../../ui";
 import { isEmail, nullOrEmpty, r } from "../../utility";
 
-class Contact {
+export class Contact {
     #option;
     #refs;
 
@@ -191,7 +191,7 @@ class Contact {
     }
 }
 
-class CustomerRecordContact {
+export class CustomerRecordContact {
     #option;
     #grid;
 
@@ -252,6 +252,4 @@ class CustomerRecordContact {
             this.#grid.source = contacts;
         }
     }
-}
-
-export { Contact, CustomerRecordContact };
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/lib/app/communications/customer.js b/lib/app/communications/customer.js
index e9115fb..10b4e95 100644
--- a/lib/app/communications/customer.js
+++ b/lib/app/communications/customer.js
@@ -1,6 +1,6 @@
-import { Grid, GridColumn, createElement, setTooltip, createIcon, createCheckbox, createRadiobox, showAlert, showConfirm, Popup, createPicture, createAudio, createVideo, createPdf, createSmilefile, createVcard, createVideofile, createFile } from "../../ui";
+import { Grid, GridColumn, createElement, setTooltip, createIcon, createCheckbox, createRadiobox, showAlert, showConfirm, Popup } from "../../ui";
 import { r, nullOrEmpty, formatUrl, isEmail, isPhone } from "../../utility";
-import { createBox } from "./lib";
+import { createBox, appendMedia, fileSupported, insertFile } from "./lib";
 import { Contact, CustomerRecordContact } from "./contact";
 import Follower from "./follower";
 
@@ -35,6 +35,8 @@ class CustomerCommunication {
     #followers;
     #buttonFollower;
     #enter;
+    #fileControl;
+    #file;
     #message;
     #data = {};
     #gridContact;
@@ -118,6 +120,38 @@ class CustomerCommunication {
         }
     }
 
+    get file() { return this.#file || this.#fileControl?.files?.[0] }
+    set file(f) {
+        this.#fileControl?.remove();
+        const label = this.#container.querySelector('.file-selector>.selector-name');
+        if (f == null) {
+            this.#fileControl = null;
+            this.#file = null;
+            if (label != null) {
+                label.style.display = 'none';
+                label.innerText = '';
+                label.querySelector('.ui-tooltip-wrapper')?.remove();
+            }
+        } else {
+            if (f instanceof HTMLInputElement) {
+                this.#fileControl = f;
+                this.#file = f.files[0];
+                const link = this.#container.querySelector('.file-selector>.selector-link');
+                if (link != null) {
+                    link.appendChild(f);
+                }
+            } else {
+                this.#fileControl = null;
+                this.#file = f;
+            }
+            if (label != null) {
+                label.style.display = '';
+                label.innerText = f.name;
+                setTooltip(label, f.name);
+            }
+        }
+    }
+
     get customerName() { return this.#container.querySelector('.customer-name>.ui-input')?.value }
     set customerName(name) {
         const element = this.#container.querySelector('.customer-name>.ui-input');
@@ -400,10 +434,20 @@ class CustomerCommunication {
         //     enter.disabled = true;
         // }
         enter.addEventListener('input', () => {
-            const val = this.#enter.value;
+            const val = this.text;
             const s = String(nullOrEmpty(val) ? 0 : val.length) + '/' + String(option.maxLength);
             this.#container.querySelector('.message-bar .prompt-count').innerText = s;
         });
+        enter.addEventListener('paste', e => {
+            if (option.customerNameVisible === true) {
+                return;
+            }
+            const file = e.clipboardData.files[0];
+            if (file != null) {
+                e.preventDefault();
+                this.file = insertFile(container, file);
+            }
+        });
         this.#enter = enter;
         container.appendChild(
             createElement('div', div => {
@@ -411,6 +455,28 @@ class CustomerCommunication {
                 if (readonly === true) {
                     div.style.display = 'none';
                 }
+                div.addEventListener('dragover', e => {
+                    if (option.readonly !== true) {
+                        const item = e.dataTransfer.items[0];
+                        if (item?.kind === 'file') {
+                            e.preventDefault();
+                            if (item.type.length > 0 && fileSupported.indexOf(item.type) < 0) {
+                                e.dataTransfer.dropEffect = 'none';
+                            } else {
+                                e.dataTransfer.dropEffect = 'link';
+                            }
+                        }
+                    }
+                });
+                div.addEventListener('drop', e => {
+                    if (option.readonly !== true) {
+                        const file = e.dataTransfer.files[0];
+                        if (file != null) {
+                            e.preventDefault();
+                            this.file = insertFile(container, file);
+                        }
+                    }
+                });
             },
                 enter,
                 createElement('div', div => div.style.textAlign = 'right',
@@ -426,6 +492,41 @@ class CustomerCommunication {
                             input.className = 'ui-input';
                         })
                     ),
+                    createElement('div', selector => {
+                        selector.className = 'file-selector';
+                        if (option.customerNameVisible === true) {
+                            selector.style.display = 'none';
+                        }
+                    },
+                        createElement('div', div => {
+                            div.className = 'selector-link';
+                            div.addEventListener('click', () => {
+                                this.#fileControl?.remove();
+                                const file = createElement('input', input => {
+                                    input.type = 'file';
+                                    input.accept = fileSupported.join(',');
+                                    input.addEventListener('change', () => {
+                                        const file = insertFile(container, input.files?.[0]);
+                                        if (file != null) {
+                                            this.file = file;
+                                        }
+                                    });
+                                });
+                                div.appendChild(this.#fileControl = file);
+                                file.dispatchEvent(new MouseEvent('click'));
+                            });
+                        },
+                            createIcon('fa-regular', 'link')
+                        ),
+                        createElement('span', span => {
+                            span.className = 'selector-name';
+                            span.style.display = 'none';
+                        }),
+                        createElement('layer', layer => {
+                            layer.appendChild(createIcon('fa-regular', 'times'));
+                            layer.addEventListener('click', () => this.file = null);
+                        })
+                    ),
                     createElement('div', 'prompt-count'),
                     createElement('button', button => {
                         button.className = 'roundbtn button-send-message';
@@ -436,7 +537,7 @@ class CustomerCommunication {
                         button.appendChild(createIcon('fa-solid', 'paper-plane'));
                         setTooltip(button, r('sendMessage', 'Send Message'));
                         button.addEventListener('click', () => {
-                            const val = this.#enter.value;
+                            const val = this.text;
                             if (nullOrEmpty(val?.trim())) {
                                 const p = showAlert(r('error', 'Error'), r('messageRequired', 'Please input the message.'), 'warn');
                                 if (typeof option.onMasking === 'function') {
@@ -446,7 +547,7 @@ class CustomerCommunication {
                                 return;
                             }
                             if (typeof option.onAddMessage === 'function') {
-                                option.onAddMessage(this.#enter.value);
+                                option.onAddMessage(this.text, this.file);
                             }
                         })
                     })
@@ -1063,43 +1164,20 @@ class CustomerCommunication {
                     }
                 }));
                 const content = createElement('div', 'item-content');
+                const emoji = s => s.replace(/(=[A-Fa-f0-9]{2}){4}/, s => decodeURIComponent(s.replaceAll('=', '%')));
+                const mmsParts = createElement('div', div => div.style.display = 'none');
                 content.appendChild(createElement('span', span => {
                     if (/https?:\/\//i.test(comm.Message)) {
-                        span.innerHTML = formatUrl(comm.Message);
+                        span.innerHTML = emoji(formatUrl(comm.Message));
                     } else {
-                        span.innerText = comm.Message;
+                        span.innerText = emoji(comm.Message);
                     }
+                    span.appendChild(mmsParts);
                 }));
                 if (comm.IsMMS && comm.MMSParts?.length > 0) {
+                    mmsParts.style.display = '';
                     for (let kv of comm.MMSParts) {
-                        switch (kv.Key) {
-                            case 'application/pdf':
-                                content.appendChild(createPdf(kv.Value));
-                                break;
-                            case 'application/smil':
-                                content.appendChild(createSmilefile(kv.Value));
-                                break;
-                            case 'audio/amr':
-                                content.appendChild(createAudio(kv.Key));
-                                break;
-                            case 'image/gif':
-                            case 'image/jpeg':
-                            case 'image/png':
-                                content.appendChild(createPicture(kv.Value));
-                                break;
-                            case 'text/x-vcard':
-                                content.appendChild(createVcard(kv.Value));
-                                break;
-                            case 'video/3gpp':
-                                content.appendChild(createVideofile(kv.Value));
-                                break;
-                            case 'video/mp4':
-                                content.appendChild(createVideo(kv.Value));
-                                break;
-                            default:
-                                content.appendChild(createFile(kv.Value));
-                                break;
-                        }
+                        appendMedia(mmsParts, kv.Key, kv.Value);
                     }
                 }
                 if (comm.IsReply) {
diff --git a/lib/app/communications/internal.js b/lib/app/communications/internal.js
index b970233..1ea5eaa 100644
--- a/lib/app/communications/internal.js
+++ b/lib/app/communications/internal.js
@@ -1,11 +1,13 @@
-import { createElement, setTooltip, createIcon } from "../../ui";
+import { createElement, setTooltip, createIcon, showAlert } from "../../ui";
 import { r, nullOrEmpty, escapeHtml } from "../../utility";
-import { createBox } from "./lib";
+import { createBox, appendMedia, fileSupported, insertFile } from "./lib";
 
 class InternalComment {
     #container;
     #option;
     #enter;
+    #fileControl;
+    #file;
     #message;
 
     constructor(opt) {
@@ -22,6 +24,50 @@ class InternalComment {
         }
     }
 
+    /**
+     * @param {boolean} flag
+     */
+    set loading(flag) {
+        if (this.#container == null) {
+            return;
+        }
+        this.#enter.disabled = flag;
+        this.#container.querySelector('.button-send-message').disabled = flag;
+        this.#container.querySelector('.button-post-note').disabled = flag;
+    }
+
+    get file() { return this.#file || this.#fileControl?.files?.[0] }
+    set file(f) {
+        this.#fileControl?.remove();
+        const label = this.#container.querySelector('.file-selector>.selector-name');
+        if (f == null) {
+            this.#fileControl = null;
+            this.#file = null;
+            if (label != null) {
+                label.style.display = 'none';
+                label.innerText = '';
+                label.querySelector('.ui-tooltip-wrapper')?.remove();
+            }
+        } else {
+            if (f instanceof HTMLInputElement) {
+                this.#fileControl = f;
+                this.#file = f.files[0];
+                const link = this.#container.querySelector('.file-selector>.selector-link');
+                if (link != null) {
+                    link.appendChild(f);
+                }
+            } else {
+                this.#fileControl = null;
+                this.#file = f;
+            }
+            if (label != null) {
+                label.style.display = '';
+                label.innerText = f.name;
+                setTooltip(label, f.name);
+            }
+        }
+    }
+
     /**
      * @param {boolean} flag
      */
@@ -50,18 +96,85 @@ class InternalComment {
         enter.placeholder = r('typeComment', 'Enter Comment Here');
         enter.maxLength = this.#option.maxLength ??= 3000;
         enter.addEventListener('input', () => {
-            const val = this.#enter.value;
+            const val = this.text;
             const s = String(nullOrEmpty(val) ? 0 : val.length) + '/' + String(this.#option.maxLength);
             this.#container.querySelector('.message-bar .prompt-count').innerText = s;
         });
         if (readonly === true) {
             enter.disabled = true;
         }
+        // enter.addEventListener('paste', e => {
+        //     const file = e.clipboardData.files[0];
+        //     if (file != null) {
+        //         e.preventDefault();
+        //         this.file = insertFile(container, file);
+        //     }
+        // });
         this.#enter = enter;
         container.appendChild(
-            createElement('div', 'message-bar',
+            createElement('div', div => {
+                div.className = 'message-bar';
+                // div.addEventListener('dragover', e => {
+                //     if (option.readonly !== true) {
+                //         const item = e.dataTransfer.items[0];
+                //         if (item?.kind === 'file') {
+                //             e.preventDefault();
+                //             if (item.type.length > 0 && fileSupported.indexOf(item.type) < 0) {
+                //                 e.dataTransfer.dropEffect = 'none';
+                //             } else {
+                //                 e.dataTransfer.dropEffect = 'link';
+                //             }
+                //         }
+                //     }
+                // });
+                // div.addEventListener('drop', e => {
+                //     if (option.readonly !== true) {
+                //         const file = e.dataTransfer.files[0];
+                //         if (file != null) {
+                //             e.preventDefault();
+                //             this.file = insertFile(container, file);
+                //         }
+                //     }
+                // });
+            },
                 enter,
                 createElement('div', div => div.style.textAlign = 'right',
+                    createElement('div', selector => {
+                        selector.className = 'file-selector';
+                        if (this.#option.noMessage === true) {
+                            selector.style.display = 'none';
+                        }
+                    },
+                        createElement('div', div => {
+                            div.className = 'selector-link';
+                            div.style.display = 'none';
+                            // div.addEventListener('click', () => {
+                            //     this.#fileControl?.remove();
+                            //     const file = createElement('input', input => {
+                            //         input.type = 'file';
+                            //         input.accept = fileSupported.join(',');
+                            //         input.addEventListener('change', () => {
+                            //             const file = insertFile(container, input.files?.[0]);
+                            //             if (file != null) {
+                            //                 this.file = file;
+                            //             }
+                            //         });
+                            //     });
+                            //     div.appendChild(this.#fileControl = file);
+                            //     file.dispatchEvent(new MouseEvent('click'));
+                            // });
+                        },
+                            createIcon('fa-regular', 'link')
+                        ),
+                        createElement('span', span => {
+                            span.className = 'selector-name';
+                            span.style.display = 'none';
+                        }),
+                        createElement('layer', layer => {
+                            layer.appendChild(createIcon('fa-regular', 'times'));
+                            layer.addEventListener('click', () => this.file = null);
+                        })
+                    ),
                     createElement('div', 'prompt-count'),
                     createElement('button', button => {
                         button.className = 'roundbtn button-send-message';
@@ -73,7 +186,7 @@ class InternalComment {
                         setTooltip(button, r('sendMessage', 'Send Message'));
                         button.addEventListener('click', () => {
                             if (typeof this.#option.onAddMessage === 'function') {
-                                this.#option.onAddMessage(this.#enter.value);
+                                this.#option.onAddMessage(this.text);
                             }
                         })
                     }),
@@ -88,7 +201,7 @@ class InternalComment {
                         setTooltip(button, r('postNote', 'Post Note'));
                         button.addEventListener('click', () => {
                             if (typeof this.#option.onAddComment === 'function') {
-                                this.#option.onAddComment(this.#enter.value);
+                                this.#option.onAddComment(this.text, this.file);
                             }
                         })
                     })
@@ -115,7 +228,15 @@ class InternalComment {
                     div.innerText = comment.UserName;
                 }));
                 const content = createElement('div', 'item-content');
-                content.appendChild(createElement('span', span => span.innerHTML = escapeHtml(comment.Comment)));
+                const emoji = s => s.replace(/(=[A-Fa-f0-9]{2}){4}/, s => decodeURIComponent(s.replaceAll('=', '%')));
+                const mmsParts = createElement('div', div => div.style.display = 'none');
+                content.appendChild(createElement('span', span => span.innerHTML = emoji(escapeHtml(comment.Comment)), mmsParts));
+                if (comment.IsMMS && comment.MMSParts?.length > 0) {
+                    mmsParts.style.display = '';
+                    for (let kv of comment.MMSParts) {
+                        appendMedia(mmsParts, kv.Key, kv.Value);
+                    }
+                }
                 if (comment.FollowUp?.length > 0) {
                     div.classList.add('item-sent');
                     const sendto = r('sendToColon', 'Send To :') + '\r\n' + comment.FollowUp.split(';').join('\r\n');
diff --git a/lib/app/communications/lib.d.ts b/lib/app/communications/lib.d.ts
index a08b025..6acff9a 100644
--- a/lib/app/communications/lib.d.ts
+++ b/lib/app/communications/lib.d.ts
@@ -1 +1,3 @@
-export function createBox(title: HTMLElement, functions: HTMLElement[]): HTMLElement
\ No newline at end of file
+export function createBox(title: HTMLElement, functions: HTMLElement[]): HTMLElement
+
+export function appendMedia(container: HTMLElement, mimeType: string, url: string): HTMLElement
\ No newline at end of file
diff --git a/lib/app/communications/lib.js b/lib/app/communications/lib.js
index 8696905..6c44c11 100644
--- a/lib/app/communications/lib.js
+++ b/lib/app/communications/lib.js
@@ -1,6 +1,7 @@
-import { createElement } from "../../ui";
+import { createElement, setTooltip, showAlert, createPicture, createAudio, createVideo, createFile } from "../../ui";
+import { r } from "../../utility";
 
-function createBox(title, functions) {
+export function createBox(title, functions) {
     const container = createElement('div', 'comm');
     const header = createElement('div', 'title-bar',
         title,
@@ -8,8 +9,115 @@ function createBox(title, functions) {
     );
     container.appendChild(header);
     return container;
-}
+};
 
-export {
-    createBox
-}
\ No newline at end of file
+export function appendMedia(container, mimeType, url) {
+    switch (mimeType) {
+        case 'application/pdf':
+            container.appendChild(createFile(url, 'file-pdf'));
+            break;
+        case 'application/smil':
+            // TODO: ignore smil files
+            // container.appendChild(createFile(url, 'smile'));
+            break;
+        case 'audio/amr':
+        case 'audio/mp3':
+        case 'audio/mpeg':
+        case 'audio/x-mpeg':
+        case 'audio/aac':
+        case 'audio/ogg':
+        case 'audio/opus':
+        case 'audio/wav':
+        case 'audio/webm':
+            container.appendChild(createAudio(mimeType, url));
+            break;
+        case 'image/gif':
+        case 'image/jpeg':
+        case 'image/png':
+            container.appendChild(createPicture(url));
+            break;
+        case 'text/x-vcard':
+            container.appendChild(createFile(url, 'id-card'));
+            break;
+        case 'video/mp4':
+        case 'video/mpeg':
+        case 'video/x-mpeg':
+        case 'video/mp2t':
+        case 'video/webm':
+        case 'video/quicktime':
+            container.appendChild(createVideo(url));
+            break;
+        default:
+            if (/^audio\//.test(mimeType)) {
+                container.appendChild(createFile(url, 'music'));
+            } else if (/^video\//.test(mimeType)) {
+                container.appendChild(createFile(url, 'video'));
+            } else {
+                container.appendChild(createFile(url));
+            }
+            break;
+    }
+    return container;
+};
+
+const MaxAttachmentSize = {
+    limit: 2 * 1024 * 1024,
+    text: '2MB'
+};
+
+export const fileSupported = [
+    'application/pdf',
+    'audio/mpeg',
+    'audio/x-mpeg',
+    'audio/amr',
+    '.amr',
+    'image/bmp',
+    'image/gif',
+    'image/jpeg',
+    'image/png',
+    'image/tiff',
+    'text/plain',
+    'text/x-vcard',
+    'video/3gpp',
+    'video/mp4',
+    'video/webm',
+    'video/quicktime'
+];
+
+export function insertFile(container, file) {
+    const label = container.querySelector('.file-selector>.selector-name');
+    if (label != null && file != null) {
+        let type = file.type;
+        if (type == null || type.length === 0) {
+            type = file.name;
+            type = type.substring(type.lastIndexOf('.'));
+        }
+        if (fileSupported.indexOf(type) < 0) {
+            showAlert(r('error', 'Error'), r('notSupported', 'File type "{type}" is now not supported.').replace('{type}', type));
+            return;
+        }
+        const isImage = /^image\//.test(type);
+        if (!isImage && file.size > MaxAttachmentSize.limit) {
+            showAlert(r('error', 'Error'), r('fileTooLarge', `File is too large. (> ${MaxAttachmentSize.text})`), 'warn');
+            return;
+        }
+        const fn = file.name;
+        label.style.display = '';
+        label.innerText = fn;
+        if (isImage) {
+            const img = new Image();
+            const reader = new FileReader();
+            reader.onload = e => {
+                img.src = e.target.result;
+                setTooltip(label, img);
+            };
+            reader.onerror = () => setTooltip(label, fn);
+            reader.readAsDataURL(file);
+            // img.src = URL.createObjectURL(file);
+            // setTooltip(label, img);
+        } else {
+            setTooltip(label, fn);
+        }
+        return file;
+    }
+};
\ No newline at end of file
diff --git a/lib/app/communications/style.scss b/lib/app/communications/style.scss
index daa9158..5ddd1db 100644
--- a/lib/app/communications/style.scss
+++ b/lib/app/communications/style.scss
@@ -290,6 +290,61 @@
                 }
             }
 
+            >.file-selector {
+                float: left;
+                display: inline-flex;
+                align-items: center;
+                height: 30px;
+
+                >.selector-link {
+                    cursor: pointer;
+                    display: flex;
+
+                    >svg {
+                        width: 16px;
+                        height: 16px;
+                        fill: var(--secondary-link-color);
+                    }
+
+                    >input {
+                        display: none;
+                    }
+                }
+
+                >.selector-name {
+                    max-width: 130px;
+                    padding: 0 20px 0 4px;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
+                    white-space: nowrap;
+
+                    +layer {
+                        display: none;
+                        margin-left: -20px;
+                        cursor: pointer;
+
+                        >svg {
+                            width: 16px;
+                            height: 16px;
+                            fill: var(--red-color);
+                        }
+
+                        &:hover {
+                            display: flex;
+                        }
+                    }
+
+                    &:hover+layer {
+                        display: flex;
+                    }
+
+                    >.ui-tooltip-wrapper img {
+                        max-width: 120px;
+                        max-height: 80px;
+                    }
+                }
+            }
+
             >.prompt-count {
                 display: inline-block;
                 color: var(--light-color);
@@ -340,6 +395,10 @@
                 max-width: 240px;
                 background-color: rgb(244, 244, 244);
 
+                audio[controls] {
+                    width: 220px;
+                }
+
                 a>svg {
                     width: 13px;
                     height: 13px;
diff --git a/lib/ui.js b/lib/ui.js
index 655cc96..a7e9d89 100644
--- a/lib/ui.js
+++ b/lib/ui.js
@@ -8,7 +8,7 @@ import { Dropdown } from "./ui/dropdown";
 import { Grid } from "./ui/grid/grid";
 import { GridColumn, GridInputColumn, GridDropdownColumn, GridCheckboxColumn, GridIconColumn, GridTextColumn } from './ui/grid/column';
 import { Popup, createPopup, showAlert, showConfirm } from "./ui/popup";
-import { createPicture, createAudio, createVideo, createPdf, createSmilefile, createVcard, createVideofile, createFile } from './ui/media';
+import { createPicture, createAudio, createVideo, createFile } from './ui/media';
 
 export {
   createElement,
@@ -42,9 +42,5 @@ export {
   createPicture,
   createAudio,
   createVideo,
-  createPdf,
-  createSmilefile,
-  createVcard,
-  createVideofile,
   createFile
 }
diff --git a/lib/ui/checkbox.js b/lib/ui/checkbox.js
index 324fb5c..0f04cf2 100644
--- a/lib/ui/checkbox.js
+++ b/lib/ui/checkbox.js
@@ -29,7 +29,7 @@ function fillCheckbox(container, type = 'fa-regular', label, tabindex = -1, char
     }
 }
 
-function createRadiobox(opts = {}) {
+export function createRadiobox(opts = {}) {
     const container = createElement('label', 'ui-check-wrapper ui-radio-wrapper',
         createElement('input', input => {
             input.setAttribute('type', 'radio');
@@ -56,7 +56,7 @@ function createRadiobox(opts = {}) {
     return container;
 }
 
-function createCheckbox(opts = {}) {
+export function createCheckbox(opts = {}) {
     const container = createElement('label', 'ui-check-wrapper',
         createElement('input', input => {
             input.setAttribute('type', 'checkbox');
@@ -97,7 +97,7 @@ function createCheckbox(opts = {}) {
     return container;
 }
 
-function resolveCheckbox(container = document.body, legacy) {
+export function resolveCheckbox(container = document.body, legacy) {
     if (legacy) {
         const checks = container.querySelectorAll('input[type="checkbox"]');
         for (let chk of checks) {
@@ -177,10 +177,4 @@ function resolveCheckbox(container = document.body, legacy) {
         box.insertBefore(input, box.firstChild);
     }
     return container;
-}
-
-export {
-    createCheckbox,
-    resolveCheckbox,
-    createRadiobox
 }
\ No newline at end of file
diff --git a/lib/ui/css/media.scss b/lib/ui/css/media.scss
index d14da9f..c763eae 100644
--- a/lib/ui/css/media.scss
+++ b/lib/ui/css/media.scss
@@ -8,9 +8,9 @@
     align-items: center;
 
     >svg {
-        width: 26px;
-        height: 26px;
-        fill: var(--link-color);
+        width: 20px;
+        height: 20px;
+        fill: var(--secondary-link-color);
     }
 
     >a {
diff --git a/lib/ui/css/variables/definition.scss b/lib/ui/css/variables/definition.scss
index 2488eb1..763d3bd 100644
--- a/lib/ui/css/variables/definition.scss
+++ b/lib/ui/css/variables/definition.scss
@@ -27,6 +27,7 @@
     --title-bg-color: rgb(68, 114, 196);
     --hover-bg-color: #eee;
     --link-color: #1890ff;
+    --secondary-link-color: #1d9ac0;
     --primary-color: rgb(123, 28, 33);
     --loading-bg-color: hsla(0, 0%, 100%, .4);
     --loading-fore-color: rgba(0, 0, 0, .2);
diff --git a/lib/ui/icon.js b/lib/ui/icon.js
index 4c085c2..a5d0eb0 100644
--- a/lib/ui/icon.js
+++ b/lib/ui/icon.js
@@ -9,14 +9,14 @@ function createUse(type, id) {
     return use;
 }
 
-function changeIcon(svg, type, id) {
+export function changeIcon(svg, type, id) {
     if (svg instanceof SVGElement) {
         svg.replaceChildren(createUse(type, id));
     }
     return svg;
 }
 
-function createIcon(type, id, style) {
+export function createIcon(type, id, style) {
     const svg = document.createElementNS(svgns, 'svg');
     svg.appendChild(createUse(type, id));
     if (style != null) {
@@ -27,7 +27,7 @@ function createIcon(type, id, style) {
     return svg;
 }
 
-function resolveIcon(container) {
+export function resolveIcon(container) {
     const svgs = container.querySelectorAll('svg[data-id]');
     for (let icon of svgs) {
         const type = icon.dataset.type;
@@ -37,10 +37,4 @@ function resolveIcon(container) {
         icon.removeAttribute('data-id');
     }
     return container;
-}
-
-export {
-    createIcon,
-    changeIcon,
-    resolveIcon
 }
\ No newline at end of file
diff --git a/lib/ui/media.d.ts b/lib/ui/media.d.ts
index 06dd9d3..9e3639b 100644
--- a/lib/ui/media.d.ts
+++ b/lib/ui/media.d.ts
@@ -19,31 +19,8 @@ export function createAudio(mime: string, url: string): HTMLAudioElement | HTMLD
 export function createVideo(url: string): HTMLVideoElement
 
 /**
- * 创建一个 pdf 文件元素
- * @param url pdf 文件 url
- */
-export function createPdf(url: string): HTMLDivElement
-
-/**
- * 创建一个 smil 文件元素
- * @param url smil 文件 url
- */
-export function createSmilefile(url: string): HTMLDivElement
-
-/**
- * 创建一个 vcard 文件元素
- * @param url vcard 文件 url
- */
-export function createVcard(url: string): HTMLDivElement
-
-/**
- * 创建一个不支持的视频文件元素
- * @param url 视频 url
- */
-export function createVideofile(url: string): HTMLDivElement
-
-/**
- * 创建一个通用文件元素
+ * 创建一个文件元素
  * @param url 文件 url
+ * @param icon 图标,默认为 `file-alt`
  */
-export function createFile(url: string): HTMLDivElement
\ No newline at end of file
+export function createFile(url: string, icon?: string): HTMLDivElement
\ No newline at end of file
diff --git a/lib/ui/media.js b/lib/ui/media.js
index aa4f651..1d32e35 100644
--- a/lib/ui/media.js
+++ b/lib/ui/media.js
@@ -74,18 +74,18 @@ export function createAudio(mime, url) {
         let timer;
         return createElement('div', 'ui-media-audio',
             createElement('button', button => {
+                button.className = 'play';
                 button.addEventListener('click', () => {
                     if (context != null) {
                         clearInterval(timer);
                         context.close();
                         context = null;
                         timestamp.textContent = '00:00 / 00:00';
+                        button.className = 'play';
                         button.replaceChildren(createIcon('fa-solid', 'play'));
                         return;
                     }
-                    get(url, {
-                        accept: mime
-                    })
+                    get(url, { accept: mime })
                         .then(r => r.blob())
                         .then(r => readBlob(r))
                         .then(r => playAmrArray(r))
@@ -93,10 +93,12 @@ export function createAudio(mime, url) {
                             context = null;
                             clearInterval(timer);
                             timestamp.textContent = '00:00 / ' + getTimeLabel(ctx.duration);
+                            button.className = 'play';
                             button.replaceChildren(createIcon('fa-solid', 'play'));
                         }))
                         .then(ctx => {
                             context = ctx;
+                            button.className = 'stop';
                             button.replaceChildren(createIcon('fa-solid', 'stop'));
                             const total = getTimeLabel(ctx.duration);
                             const refresh = () => timestamp.textContent = getTimeLabel(ctx.currentTime) + ' / ' + total;
@@ -128,8 +130,8 @@ export function createVideo(url) {
     });
 }
 
-function createFileElement(url, icon) {
-    return createElement('div', 'ui-media-file',
+export function createFile(url, icon = 'file-alt') {
+    return createElement('div', `ui-media-file ${icon}`,
         createIcon('fa-solid', icon),
         createElement('a', a => {
             a.target = '_blank';
@@ -137,24 +139,4 @@ function createFileElement(url, icon) {
             a.innerText = 'Click here to view the file';
         })
     );
-}
-
-export function createPdf(url) {
-    return createFileElement(url, 'file-pdf');
-}
-
-export function createSmilefile(url) {
-    return createFileElement(url, 'smile');
-}
-
-export function createVcard(url) {
-    return createFileElement(url, 'id-card');
-}
-
-export function createVideofile(url) {
-    return createFileElement(url, 'photo-video');
-}
-
-export function createFile(url) {
-    return createFileElement(url, 'file-alt');
 }
\ No newline at end of file
diff --git a/lib/utility/cookie.js b/lib/utility/cookie.js
index b8b3b93..295692b 100644
--- a/lib/utility/cookie.js
+++ b/lib/utility/cookie.js
@@ -1,4 +1,4 @@
-function setCookie(name, value, expireDays) {
+export function setCookie(name, value, expireDays) {
     if (name == null) {
         return;
     }
@@ -14,7 +14,7 @@ function setCookie(name, value, expireDays) {
     document.cookie = `${name}=${encodeURIComponent(value)}${extra}`;
 }
 
-function getCookie(name) {
+export function getCookie(name) {
     if (name == null) {
         return null;
     }
@@ -29,12 +29,6 @@ function getCookie(name) {
     return null;
 }
 
-function deleteCookie(name) {
+export function deleteCookie(name) {
     setCookie(name, '', -1);
-}
-
-export {
-    getCookie,
-    setCookie,
-    deleteCookie
 }
\ No newline at end of file
diff --git a/lib/utility/lgres.js b/lib/utility/lgres.js
index 4019bf9..fe6aa2f 100644
--- a/lib/utility/lgres.js
+++ b/lib/utility/lgres.js
@@ -98,7 +98,7 @@ function applyLanguage(dom, result) {
     }
 }
 
-async function init(dom = document.body, options = {}) {
+export async function init(dom = document.body, options = {}) {
     const lgid = getCurrentLgId();
     let lgres = localStorage.getItem(getStorageKey(lgid));
     let result;
@@ -139,14 +139,14 @@ async function init(dom = document.body, options = {}) {
     }
 }
 
-function r(key, defaultValue) {
+export function r(key, defaultValue) {
     if (cache != null) {
         return getLanguage(cache, key, defaultValue);
     }
     return defaultValue;
 }
 
-const lang = {
+export const lang = {
     get current() {
         return getCurrentLgId();
     },
@@ -156,10 +156,4 @@ const lang = {
     get savedSuccessfully() {
         return r('savedSuccessfully', 'Saved successfully.');
     }
-}
-
-export {
-    init,
-    r,
-    lang
 }
\ No newline at end of file
diff --git a/lib/utility/request.js b/lib/utility/request.js
index 74edca3..7796ba0 100644
--- a/lib/utility/request.js
+++ b/lib/utility/request.js
@@ -8,7 +8,7 @@ function combineUrl(url) {
     return (consts.path || '') + url;
 }
 
-function get(url, options = {}) {
+export function get(url, options = {}) {
     return fetch(combineUrl(url), {
         method: options.method || 'GET',
         headers: {
@@ -21,7 +21,7 @@ function get(url, options = {}) {
     });
 }
 
-function post(url, data, options = {}) {
+export function post(url, data, options = {}) {
     // let contentType;
     if (data instanceof FormData) {
         // contentType = 'multipart/form-data';
@@ -46,7 +46,7 @@ function post(url, data, options = {}) {
     });
 }
 
-function upload(url, data, options = {}) {
+export function upload(url, data, options = {}) {
     return new Promise((resolve, reject) => {
         const request = new XMLHttpRequest();
         request.onreadystatechange = function () {
@@ -73,10 +73,4 @@ function upload(url, data, options = {}) {
         }
         request.send(data);
     });
-}
-
-export {
-    get,
-    post,
-    upload
 }
\ No newline at end of file
diff --git a/lib/utility/strings.js b/lib/utility/strings.js
index 36760e3..cc75435 100644
--- a/lib/utility/strings.js
+++ b/lib/utility/strings.js
@@ -1,8 +1,8 @@
-function nullOrEmpty(s) {
+export function nullOrEmpty(s) {
     return s == null || typeof s !== 'string' || s.length === 0;
 }
 
-function contains(s, key, ignoreCase) {
+export function contains(s, key, ignoreCase) {
     if (nullOrEmpty(s) || key == null) {
         return false;
     }
@@ -15,21 +15,21 @@ function contains(s, key, ignoreCase) {
     return s.indexOf(key) >= 0;
 }
 
-function endsWith(s, suffix) {
+export function endsWith(s, suffix) {
     if (nullOrEmpty(s) || nullOrEmpty(suffix)) {
         return false;
     }
     return s.indexOf(suffix) === s.length - suffix.length;
 }
 
-function padStart(s, num, char) {
+export function padStart(s, num, char) {
     if (nullOrEmpty(s) || isNaN(num) || num <= s.length) {
         return s;
     }
     return (char ?? ' ').repeat(num - s.length);
 }
 
-function formatUrl(msg) {
+export function formatUrl(msg) {
     //const urlReg = /(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?/ig;
     //const urlArrray = str.match(urlReg);
     const p = /(http|ftp|https):\/\/.+?(\s|\r\n|\r|\n|\"|\'|\*|$)/g;
@@ -53,7 +53,7 @@ function formatUrl(msg) {
     return msg;
 }
 
-function escapeHtml(text) {
+export function escapeHtml(text) {
     if (text == null) {
         return '';
     }
@@ -64,13 +64,4 @@ function escapeHtml(text) {
         .replaceAll('\r\n', '<br/>')
         .replaceAll('\n', '<br/>')
         .replaceAll('  ', '&nbsp;');
-}
-
-export {
-    nullOrEmpty,
-    contains,
-    endsWith,
-    padStart,
-    formatUrl,
-    escapeHtml
 }
\ No newline at end of file