add favicon
This commit is contained in:
1 parent
a74fe7a388
commit
5ac6281304
7 files changed
+147
No files matched your search
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
onActivated,
|
||||
onMounted,
|
||||
toValue,
|
||||
watch,
|
||||
type MaybeRefOrGetter,
|
||||
} from 'vue';
|
||||
import { APP_DISPLAY_NAME } from '@/config/app';
|
||||
import { faviconHrefFor, setFavicon } from '@/config/favicons';
|
||||
|
||||
export function formatPageTitle(filename?: string | null): string {
|
||||
const name = filename?.trim();
|
||||
return name ? `${name} - ${APP_DISPLAY_NAME}` : APP_DISPLAY_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets `document.title` to `filename - Sejong Office` (or just the app name)
|
||||
* and swaps the favicon by file extension (home icon when no file).
|
||||
*/
|
||||
export function usePageTitle(
|
||||
filename: MaybeRefOrGetter<string | null | undefined> = null,
|
||||
) {
|
||||
function apply() {
|
||||
const name = toValue(filename);
|
||||
document.title = formatPageTitle(name);
|
||||
setFavicon(faviconHrefFor(name));
|
||||
}
|
||||
|
||||
watch(() => toValue(filename), apply, { immediate: true });
|
||||
onMounted(apply);
|
||||
onActivated(apply);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Favicon SVGs based on Material Design Icons (@mdi/svg), Apache License 2.0.
|
||||
* https://github.com/Templarian/MaterialDesign
|
||||
*/
|
||||
|
||||
import { extensionOf } from '@/config/app';
|
||||
|
||||
type FaviconKind =
|
||||
| 'home'
|
||||
| 'pdf'
|
||||
| 'word'
|
||||
| 'excel'
|
||||
| 'powerpoint'
|
||||
| 'hwp'
|
||||
| 'text'
|
||||
| 'csv'
|
||||
| 'file';
|
||||
|
||||
const PATHS: Record<FaviconKind, string> = {
|
||||
home: 'M9,13H15V19H18V10L12,5.5L6,10V19H9V13M4,21V9L12,3L20,9V21H4Z',
|
||||
pdf: 'M19 3H5C3.9 3 3 3.9 3 5V19C3 20.1 3.9 21 5 21H19C20.1 21 21 20.1 21 19V5C21 3.9 20.1 3 19 3M9.5 11.5C9.5 12.3 8.8 13 8 13H7V15H5.5V9H8C8.8 9 9.5 9.7 9.5 10.5V11.5M14.5 13.5C14.5 14.3 13.8 15 13 15H10.5V9H13C13.8 9 14.5 9.7 14.5 10.5V13.5M18.5 10.5H17V11.5H18.5V13H17V15H15.5V9H18.5V10.5M12 10.5H13V13.5H12V10.5M7 10.5H8V11.5H7V10.5Z',
|
||||
word: 'M15.5,17H14L12,9.5L10,17H8.5L6.1,7H7.8L9.34,14.5L11.3,7H12.7L14.67,14.5L16.2,7H17.9M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z',
|
||||
excel:
|
||||
'M16.2,17H14.2L12,13.2L9.8,17H7.8L11,12L7.8,7H9.8L12,10.8L14.2,7H16.2L13,12M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z',
|
||||
powerpoint:
|
||||
'M9.8,13.4H12.3C13.8,13.4 14.46,13.12 15.1,12.58C15.74,12.03 16,11.25 16,10.23C16,9.26 15.75,8.5 15.1,7.88C14.45,7.29 13.83,7 12.3,7H8V17H9.8V13.4M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5C3,3.89 3.9,3 5,3H19M9.8,12V8.4H12.1C12.76,8.4 13.27,8.65 13.6,9C13.93,9.35 14.1,9.72 14.1,10.24C14.1,10.8 13.92,11.19 13.6,11.5C13.28,11.81 12.9,12 12.22,12H9.8Z',
|
||||
hwp: 'M17,4V10L15,8L13,10V4H9V20H19V4H17M3,7V5H5V4C5,2.89 5.9,2 7,2H19C20.05,2 21,2.95 21,4V20C21,21.05 20.05,22 19,22H7C5.95,22 5,21.05 5,20V19H3V17H5V13H3V11H5V7H3M5,5V7H7V5H5M5,19H7V17H5V19M5,13H7V11H5V13Z',
|
||||
text: 'M6,2A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6M6,4H13V9H18V20H6V4M8,12V14H16V12H8M8,16V18H13V16H8Z',
|
||||
csv: 'M14 2H6C4.9 2 4 2.9 4 4V20C4 21.1 4.9 22 6 22H18C19.1 22 20 21.1 20 20V8L14 2M18 20H6V4H13V9H18V20M10 19L12 15H9V10H15V15L13 19H10',
|
||||
file: 'M6,2A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2H6M6,4H13V9H18V20H6V4M8,12V14H16V12H8M8,16V18H13V16H8Z',
|
||||
};
|
||||
|
||||
const COLORS: Record<FaviconKind, string> = {
|
||||
home: '#2f6b55',
|
||||
pdf: '#e53935',
|
||||
word: '#1e88e5',
|
||||
excel: '#43a047',
|
||||
powerpoint: '#fb8c00',
|
||||
hwp: '#00897b',
|
||||
text: '#607d8b',
|
||||
csv: '#00838f',
|
||||
file: '#546e7a',
|
||||
};
|
||||
|
||||
const EXT_KIND: Record<string, FaviconKind> = {
|
||||
pdf: 'pdf',
|
||||
doc: 'word',
|
||||
docx: 'word',
|
||||
odt: 'word',
|
||||
rtf: 'word',
|
||||
dotx: 'word',
|
||||
xls: 'excel',
|
||||
xlsx: 'excel',
|
||||
ods: 'excel',
|
||||
xltx: 'excel',
|
||||
csv: 'csv',
|
||||
ppt: 'powerpoint',
|
||||
pptx: 'powerpoint',
|
||||
odp: 'powerpoint',
|
||||
potx: 'powerpoint',
|
||||
hwp: 'hwp',
|
||||
hwpx: 'hwp',
|
||||
txt: 'text',
|
||||
};
|
||||
|
||||
function toDataUri(kind: FaviconKind): string {
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="${COLORS[kind]}" d="${PATHS[kind]}"/></svg>`;
|
||||
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
||||
}
|
||||
|
||||
const CACHE = new Map<FaviconKind, string>();
|
||||
|
||||
export function faviconHrefFor(filename?: string | null): string {
|
||||
const name = filename?.trim();
|
||||
let kind: FaviconKind = 'home';
|
||||
if (name) {
|
||||
const ext = extensionOf(name);
|
||||
kind = ext ? (EXT_KIND[ext] ?? 'file') : 'home';
|
||||
}
|
||||
let href = CACHE.get(kind);
|
||||
if (!href) {
|
||||
href = toDataUri(kind);
|
||||
CACHE.set(kind, href);
|
||||
}
|
||||
return href;
|
||||
}
|
||||
|
||||
export function setFavicon(href: string) {
|
||||
let link = document.querySelector<HTMLLinkElement>("link[rel='icon']");
|
||||
if (!link) {
|
||||
link = document.createElement('link');
|
||||
link.rel = 'icon';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
link.type = 'image/svg+xml';
|
||||
// Bust stubborn tab caches when switching between data URIs
|
||||
if (link.href !== href) {
|
||||
link.href = href;
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useRouter } from 'vue-router';
|
||||
import AppShell from '@/components/AppShell.vue';
|
||||
import { APP_DISPLAY_NAME, isHwp, isOffice } from '@/config/app';
|
||||
import { createSession, uploadDocument } from '@/api/client';
|
||||
import { usePageTitle } from '@/composables/usePageTitle';
|
||||
import { storeHwpFile } from '@/composables/useSessionLifecycle';
|
||||
|
||||
const router = useRouter();
|
||||
@@ -11,6 +12,8 @@ const dragging = ref(false);
|
||||
const error = ref('');
|
||||
const busy = ref(false);
|
||||
|
||||
usePageTitle();
|
||||
|
||||
async function handleFile(file: File | undefined) {
|
||||
if (!file || busy.value) return;
|
||||
error.value = '';
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import AppShell from '@/components/AppShell.vue';
|
||||
import { APP_DISPLAY_NAME, APP_GIT_URL, APP_NAME } from '@/config/app';
|
||||
import { usePageTitle } from '@/composables/usePageTitle';
|
||||
|
||||
usePageTitle('오픈소스 라이선스');
|
||||
|
||||
const thirdParty = [
|
||||
{ name: 'Vue', license: 'MIT', url: 'https://github.com/vuejs/core' },
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRoute, useRouter } from 'vue-router';
|
||||
import AppShell from '@/components/AppShell.vue';
|
||||
import EditorErrorState from '@/components/EditorErrorState.vue';
|
||||
import { fetchEditorConfig } from '@/api/client';
|
||||
import { usePageTitle } from '@/composables/usePageTitle';
|
||||
import { useSessionLifecycle } from '@/composables/useSessionLifecycle';
|
||||
|
||||
defineOptions({ name: 'OnlyOfficeEditorView' });
|
||||
@@ -18,6 +19,7 @@ let scriptEl: HTMLScriptElement | null = null;
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
let initialized = false;
|
||||
|
||||
usePageTitle(filename);
|
||||
useSessionLifecycle({ enableHeartbeat: true });
|
||||
|
||||
function editorHeightPx(): number {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { createEditor, type RhwpEditor } from '@rhwp/editor';
|
||||
import AppShell from '@/components/AppShell.vue';
|
||||
import EditorErrorState from '@/components/EditorErrorState.vue';
|
||||
import { ApiError } from '@/api/client';
|
||||
import { usePageTitle } from '@/composables/usePageTitle';
|
||||
import {
|
||||
clearHwpFile,
|
||||
loadHwpFile,
|
||||
@@ -19,6 +20,7 @@ const filename = ref('문서.hwp');
|
||||
let editor: RhwpEditor | null = null;
|
||||
let initialized = false;
|
||||
|
||||
usePageTitle(filename);
|
||||
useSessionLifecycle({ enableHeartbeat: true });
|
||||
|
||||
onMounted(async () => {
|
||||
|
||||
Reference in new issue
Block a user