mirror of
https://github.com/pionxzh/chatgpt-exporter.git
synced 2026-07-23 09:00:51 -05:00
feat: add conversation timestamp on page and HTML, revamp setting UI
closes #54, closes #104
This commit is contained in:
@@ -2,5 +2,7 @@ export const baseUrl = 'https://chat.openai.com'
|
||||
|
||||
export const LEGACY_KEY_FILENAME_FORMAT = 'exporter-format'
|
||||
export const KEY_FILENAME_FORMAT = 'exporter:filename_format'
|
||||
export const KEY_TIMESTAMP_ENABLED = 'exporter:enable_timestamp'
|
||||
export const KEY_TIMESTAMP_24H = 'exporter:timestamp_24h'
|
||||
export const KEY_META_ENABLED = 'exporter:enable_meta'
|
||||
export const KEY_META_LIST = 'exporter:meta_list'
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import JSZip from 'jszip'
|
||||
import { fetchConversation, getCurrentChatId, processConversation } from '../api'
|
||||
import { baseUrl } from '../constants'
|
||||
import { KEY_TIMESTAMP_24H, KEY_TIMESTAMP_ENABLED, baseUrl } from '../constants'
|
||||
import { checkIfConversationStarted, getConversationChoice, getUserAvatar } from '../page'
|
||||
import templateHtml from '../template.html?raw'
|
||||
import { downloadFile, getFileNameWithFormat } from '../utils/download'
|
||||
import { fromMarkdown, toHtml } from '../utils/markdown'
|
||||
import { ScriptStorage } from '../utils/storage'
|
||||
import { standardizeLineBreaks } from '../utils/text'
|
||||
import { dateStr, getColorScheme, timestamp } from '../utils/utils'
|
||||
import type { ApiConversationWithId, ConversationResult } from '../api'
|
||||
import type { ExportMeta } from '../ui/MetaContext'
|
||||
import type { ExportMeta } from '../ui/SettingContext'
|
||||
|
||||
export async function exportToHtml(fileNameFormat: string, metaList: ExportMeta[]) {
|
||||
if (!checkIfConversationStarted()) {
|
||||
@@ -71,17 +72,22 @@ function conversationToHtml(conversation: ConversationResult, avatar: string, me
|
||||
conversationContent = `<p>${escapeHtml(content)}</p>`
|
||||
}
|
||||
|
||||
const enableTimestamp = ScriptStorage.get<boolean>(KEY_TIMESTAMP_ENABLED) ?? false
|
||||
const timeStamp24H = ScriptStorage.get<boolean>(KEY_TIMESTAMP_24H) ?? false
|
||||
const timestamp = item.message?.create_time ?? ''
|
||||
const showTimestamp = enableTimestamp && timestamp
|
||||
let conversationDate = ''
|
||||
let conversationTime = ''
|
||||
|
||||
if (timestamp) {
|
||||
if (showTimestamp) {
|
||||
const date = new Date(timestamp * 1000)
|
||||
const isoStr = date.toISOString()
|
||||
// format: 2022-01-01 10:12:00 UTC
|
||||
conversationDate = `${isoStr.split('T')[0]} ${isoStr.split('T')[1].split('.')[0]} UTC`
|
||||
// format: 10:12 AM
|
||||
conversationTime = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })
|
||||
// format: 20:12 / 08:12 PM
|
||||
conversationTime = timeStamp24H
|
||||
? date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false })
|
||||
: date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
return `
|
||||
@@ -94,7 +100,7 @@ function conversationToHtml(conversation: ConversationResult, avatar: string, me
|
||||
${conversationContent}
|
||||
</div>
|
||||
</div>
|
||||
${timestamp ? `<div class="time" title="${conversationDate}">${conversationTime}</div>` : ''}
|
||||
${showTimestamp ? `<div class="time" title="${conversationDate}">${conversationTime}</div>` : ''}
|
||||
</div>`
|
||||
}).join('\n\n')
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { fromMarkdown, toMarkdown } from '../utils/markdown'
|
||||
import { standardizeLineBreaks } from '../utils/text'
|
||||
import { dateStr, timestamp } from '../utils/utils'
|
||||
import type { ApiConversationWithId, ConversationResult } from '../api'
|
||||
import type { ExportMeta } from '../ui/MetaContext'
|
||||
import type { ExportMeta } from '../ui/SettingContext'
|
||||
|
||||
export async function exportToMarkdown(fileNameFormat: string, metaList: ExportMeta[]) {
|
||||
if (!checkIfConversationStarted()) {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { render } from 'preact'
|
||||
import sentinel from 'sentinel-js'
|
||||
import { GM_deleteValue, GM_getValue, GM_setValue } from 'vite-plugin-monkey/dist/client'
|
||||
import { fetchConversation, processConversation } from './api'
|
||||
import { KEY_FILENAME_FORMAT, LEGACY_KEY_FILENAME_FORMAT } from './constants'
|
||||
import { getChatIdFromUrl, getConversationChoice } from './page'
|
||||
import { Menu } from './ui/Menu'
|
||||
import { SecondaryToolbar } from './ui/SecondaryToolbar'
|
||||
import { onloadSafe } from './utils/utils'
|
||||
@@ -67,7 +69,7 @@ function main() {
|
||||
})
|
||||
|
||||
/** Insert copy button to the next of feedback buttons */
|
||||
sentinel.on('.flex.justify-between', (node) => {
|
||||
sentinel.on('main .flex.justify-between', (node) => {
|
||||
if (!node.querySelector('button')) return
|
||||
// ignore codeblock
|
||||
if (node.closest('pre')) return
|
||||
@@ -79,5 +81,40 @@ function main() {
|
||||
render(<SecondaryToolbar index={index} />, secondaryToolbar)
|
||||
node.append(secondaryToolbar)
|
||||
})
|
||||
|
||||
/** Insert timestamp to the bottom right of each message */
|
||||
let chatId = ''
|
||||
sentinel.on('main .group', async () => {
|
||||
const threadContents = Array.from(document.querySelectorAll('main .group > .text-base > .relative:nth-child(2)'))
|
||||
|
||||
const currentChatId = getChatIdFromUrl()
|
||||
if (!currentChatId || currentChatId === chatId) return
|
||||
chatId = currentChatId
|
||||
|
||||
const rawConversation = await fetchConversation(chatId)
|
||||
const conversationChoices = getConversationChoice()
|
||||
const { conversationNodes } = processConversation(rawConversation, conversationChoices)
|
||||
|
||||
threadContents.forEach((thread, index) => {
|
||||
const createTime = conversationNodes[index].message?.create_time
|
||||
if (!createTime) return
|
||||
|
||||
const date = new Date(createTime * 1000)
|
||||
|
||||
const timestamp = document.createElement('time')
|
||||
timestamp.className = 'text-gray-500 dark:text-gray-400 text-sm text-right'
|
||||
timestamp.dateTime = date.toISOString()
|
||||
timestamp.title = date.toLocaleString()
|
||||
|
||||
const hour12 = document.createElement('span')
|
||||
hour12.setAttribute('data-time-format', '12')
|
||||
hour12.textContent = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' })
|
||||
const hour24 = document.createElement('span')
|
||||
hour24.setAttribute('data-time-format', '24')
|
||||
hour24.textContent = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false })
|
||||
timestamp.append(hour12, hour24)
|
||||
thread.append(timestamp)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,18 @@ p > img[src*="https://images.unsplash.com/"] {
|
||||
animation: fadeIn .3s;
|
||||
}
|
||||
|
||||
span[data-time-format] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body[data-time-format="12"] span[data-time-format="12"] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
body[data-time-format="24"] span[data-time-format="24"] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.Select {
|
||||
padding: 0 0 0 0.5rem;
|
||||
width: 7.5rem;
|
||||
|
||||
@@ -1,3 +1,62 @@
|
||||
.after\:content-\[\'\'\]:after {
|
||||
--tw-content: "";
|
||||
content: var(--tw-content);
|
||||
}
|
||||
|
||||
.after\:transition-all:after {
|
||||
content: var(--tw-content);
|
||||
transition-duration: .15s;
|
||||
transition-property: all;
|
||||
transition-timing-function: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
.after\:bg-white:after {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255/var(--tw-bg-opacity));
|
||||
content: var(--tw-content);
|
||||
}
|
||||
|
||||
.after\:border-gray-300:after {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(209 213 219/var(--tw-border-opacity));
|
||||
content: var(--tw-content);
|
||||
}
|
||||
|
||||
.after\:border:after {
|
||||
border-width: 1px;
|
||||
content: var(--tw-content);
|
||||
}
|
||||
|
||||
.after\:rounded-full:after {
|
||||
border-radius: 9999px;
|
||||
|
||||
content: var(--tw-content);
|
||||
}
|
||||
.after\:w-4:after {
|
||||
content: var(--tw-content);
|
||||
width: 1rem
|
||||
}
|
||||
|
||||
.after\:h-4:after {
|
||||
content: var(--tw-content);
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.after\:top-\[2px\]:after {
|
||||
content: var(--tw-content);
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.after\:left-\[2px\]:after {
|
||||
content: var(--tw-content);
|
||||
left: 2px;
|
||||
}
|
||||
|
||||
.after\:absolute:after {
|
||||
content: var(--tw-content);
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn .3s;
|
||||
}
|
||||
@@ -6,11 +65,6 @@
|
||||
animation: slideUp .3s;
|
||||
}
|
||||
|
||||
.bg-gray-200 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(229 231 235/var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(28 100 242/var(--tw-bg-opacity));
|
||||
@@ -24,13 +78,22 @@
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.dark .dark\:bg-white\/5 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / 5%);
|
||||
}
|
||||
|
||||
.dark .dark\:border-gray-\[\#86858d\] {
|
||||
border-color: #86858d;
|
||||
}
|
||||
|
||||
.dark .dark\:hover\:bg-gray-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(55 65 81/var(--tw-bg-opacity));
|
||||
.dark .dark\:border-gray-600 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(75 85 99/var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.empty\:hidden:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fill-current {
|
||||
@@ -45,6 +108,14 @@
|
||||
height: 0.625rem;
|
||||
}
|
||||
|
||||
.h-4 {
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.mr-8 {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
@@ -53,6 +124,26 @@
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.peer:checked~.peer-checked\:bg-blue-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(28 100 242/var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.peer:checked~.peer-checked\:after\:border-white:after {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(255 255 255/var(--tw-border-opacity));
|
||||
content: var(--tw-content);
|
||||
}
|
||||
.peer:checked~.peer-checked\:after\:translate-x-full:after {
|
||||
--tw-translate-x: 100%;
|
||||
content: var(--tw-content);
|
||||
transform: translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
|
||||
.pl-4 {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.pr-8 {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
@@ -77,6 +168,10 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.w-9 {
|
||||
width: 2.25rem;
|
||||
}
|
||||
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
}
|
||||
|
||||
.DialogContent {
|
||||
background-color: white;
|
||||
background-color: #f3f3f3;
|
||||
border-radius: 6px;
|
||||
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
|
||||
position: fixed;
|
||||
@@ -15,7 +15,7 @@
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 90vw;
|
||||
max-width: 450px;
|
||||
max-width: 560px;
|
||||
max-height: 85vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -7,7 +7,7 @@ import { exportAllToMarkdown } from '../exporter/markdown'
|
||||
import { RequestQueue } from '../utils/queue'
|
||||
import { CheckBox } from './CheckBox'
|
||||
import { IconCross } from './Icons'
|
||||
import { useMetaDataContext } from './MetaContext'
|
||||
import { useSettingContext } from './SettingContext'
|
||||
import type { ApiConversationItem, ApiConversationWithId } from '../api'
|
||||
import type { FC } from '../type'
|
||||
|
||||
@@ -76,7 +76,7 @@ interface ExportDialogProps {
|
||||
}
|
||||
|
||||
export const ExportDialog: FC<ExportDialogProps> = ({ format, open, onOpenChange, children }) => {
|
||||
const { enableMeta, exportMetaList } = useMetaDataContext()
|
||||
const { enableMeta, exportMetaList } = useSettingContext()
|
||||
const metaList = useMemo(() => enableMeta ? exportMetaList : [], [enableMeta, exportMetaList])
|
||||
|
||||
const [conversations, setConversations] = useState<ApiConversationItem[]>([])
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { createContext, useContext } from 'preact/compat'
|
||||
import { KEY_FILENAME_FORMAT } from '../constants'
|
||||
import { useGMStorage } from '../hooks/useGMStorage'
|
||||
import type { FC } from 'preact/compat'
|
||||
|
||||
const defaultFormat = 'ChatGPT-{title}'
|
||||
|
||||
const FormatContext = createContext({
|
||||
format: defaultFormat,
|
||||
setFormat: (_: string) => {},
|
||||
})
|
||||
|
||||
export const FormatProvider: FC = ({ children }) => {
|
||||
const [format, setFormat] = useGMStorage(KEY_FILENAME_FORMAT, defaultFormat)
|
||||
|
||||
return (
|
||||
<FormatContext.Provider
|
||||
value={{ format, setFormat }}
|
||||
>
|
||||
{children}
|
||||
</FormatContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useFormatContext = () => useContext(FormatContext)
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as HoverCard from '@radix-ui/react-hover-card'
|
||||
import { useCallback, useMemo, useState } from 'preact/hooks'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||
import { exportToHtml } from '../exporter/html'
|
||||
import { exportToPng } from '../exporter/image'
|
||||
import { exportToJson } from '../exporter/json'
|
||||
@@ -8,10 +8,9 @@ import { exportToText } from '../exporter/text'
|
||||
import { getHistoryDisabled } from '../page'
|
||||
import { Divider } from './Divider'
|
||||
import { ExportDialog } from './ExportDialog'
|
||||
import { FormatProvider, useFormatContext } from './FormatContext'
|
||||
import { FileCode, IconArrowRightFromBracket, IconCamera, IconCopy, IconJSON, IconMarkdown, IconSetting, IconZip } from './Icons'
|
||||
import { MenuItem } from './MenuItem'
|
||||
import { MetaDataProvider, useMetaDataContext } from './MetaContext'
|
||||
import { SettingProvider, useSettingContext } from './SettingContext'
|
||||
import { SettingDialog } from './SettingDialog'
|
||||
|
||||
import '../style.css'
|
||||
@@ -28,8 +27,24 @@ function MenuInner({ container }: { container: HTMLDivElement }) {
|
||||
const [exportOpen, setExportOpen] = useState(false)
|
||||
const [settingOpen, setSettingOpen] = useState(false)
|
||||
|
||||
const { format } = useFormatContext()
|
||||
const { enableMeta, exportMetaList } = useMetaDataContext()
|
||||
const {
|
||||
format,
|
||||
enableTimestamp,
|
||||
timeStamp24H,
|
||||
enableMeta,
|
||||
exportMetaList,
|
||||
} = useSettingContext()
|
||||
|
||||
useEffect(() => {
|
||||
console.log('enableTimestamp', enableTimestamp, 'timeStamp24H', timeStamp24H)
|
||||
if (enableTimestamp) {
|
||||
document.body.setAttribute('data-time-format', timeStamp24H ? '24' : '12')
|
||||
}
|
||||
else {
|
||||
document.body.removeAttribute('data-time-format')
|
||||
}
|
||||
}, [enableTimestamp, timeStamp24H])
|
||||
|
||||
const metaList = useMemo(() => enableMeta ? exportMetaList : [], [enableMeta, exportMetaList])
|
||||
|
||||
const onClickText = useCallback(() => exportToText(), [])
|
||||
@@ -161,10 +176,8 @@ function MenuInner({ container }: { container: HTMLDivElement }) {
|
||||
|
||||
export function Menu({ container }: { container: HTMLDivElement }) {
|
||||
return (
|
||||
<FormatProvider>
|
||||
<MetaDataProvider>
|
||||
<MenuInner container={container} />
|
||||
</MetaDataProvider>
|
||||
</FormatProvider>
|
||||
<SettingProvider>
|
||||
<MenuInner container={container} />
|
||||
</SettingProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { createContext, useContext } from 'preact/compat'
|
||||
import { KEY_META_ENABLED, KEY_META_LIST } from '../constants'
|
||||
import { useGMStorage } from '../hooks/useGMStorage'
|
||||
import type { FC } from 'preact/compat'
|
||||
|
||||
export interface ExportMeta {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const defaultExportMetaList: ExportMeta[] = [
|
||||
{ name: 'title', value: '{title}' },
|
||||
{ name: 'source', value: '{source}' },
|
||||
]
|
||||
|
||||
const MetaContext = createContext({
|
||||
enableMeta: false,
|
||||
setEnableMeta: (_: boolean) => {},
|
||||
exportMetaList: defaultExportMetaList,
|
||||
setExportMetaList: (_: ExportMeta[]) => {},
|
||||
})
|
||||
|
||||
export const MetaDataProvider: FC = ({ children }) => {
|
||||
const [enableMeta, setEnableMeta] = useGMStorage(KEY_META_ENABLED, false)
|
||||
const [exportMetaList, setExportMetaList] = useGMStorage(KEY_META_LIST, defaultExportMetaList)
|
||||
|
||||
return (
|
||||
<MetaContext.Provider
|
||||
value={{
|
||||
enableMeta,
|
||||
setEnableMeta,
|
||||
exportMetaList,
|
||||
setExportMetaList,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MetaContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useMetaDataContext = () => useContext(MetaContext)
|
||||
76
packages/userscript/src/ui/SettingContext.tsx
Normal file
76
packages/userscript/src/ui/SettingContext.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { createContext, useContext } from 'preact/compat'
|
||||
import { useCallback } from 'preact/hooks'
|
||||
import { KEY_FILENAME_FORMAT, KEY_META_ENABLED, KEY_META_LIST, KEY_TIMESTAMP_24H, KEY_TIMESTAMP_ENABLED } from '../constants'
|
||||
import { useGMStorage } from '../hooks/useGMStorage'
|
||||
import type { FC } from 'preact/compat'
|
||||
|
||||
const defaultFormat = 'ChatGPT-{title}'
|
||||
|
||||
export interface ExportMeta {
|
||||
name: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const defaultExportMetaList: ExportMeta[] = [
|
||||
{ name: 'title', value: '{title}' },
|
||||
{ name: 'source', value: '{source}' },
|
||||
]
|
||||
|
||||
const SettingContext = createContext({
|
||||
format: defaultFormat,
|
||||
setFormat: (_: string) => {},
|
||||
|
||||
enableTimestamp: false,
|
||||
setEnableTimestamp: (_: boolean) => {},
|
||||
timeStamp24H: false,
|
||||
setTimeStamp24H: (_: boolean) => {},
|
||||
|
||||
enableMeta: false,
|
||||
setEnableMeta: (_: boolean) => {},
|
||||
exportMetaList: defaultExportMetaList,
|
||||
setExportMetaList: (_: ExportMeta[]) => {},
|
||||
|
||||
resetDefault: () => {},
|
||||
})
|
||||
|
||||
export const SettingProvider: FC = ({ children }) => {
|
||||
const [format, setFormat] = useGMStorage(KEY_FILENAME_FORMAT, defaultFormat)
|
||||
|
||||
const [enableTimestamp, setEnableTimestamp] = useGMStorage(KEY_TIMESTAMP_ENABLED, false)
|
||||
const [timeStamp24H, setTimeStamp24H] = useGMStorage(KEY_TIMESTAMP_24H, false)
|
||||
|
||||
const [enableMeta, setEnableMeta] = useGMStorage(KEY_META_ENABLED, false)
|
||||
|
||||
const [exportMetaList, setExportMetaList] = useGMStorage(KEY_META_LIST, defaultExportMetaList)
|
||||
|
||||
const resetDefault = useCallback(() => {
|
||||
setFormat(defaultFormat)
|
||||
setEnableMeta(false)
|
||||
setExportMetaList(defaultExportMetaList)
|
||||
}, [setFormat, setEnableMeta, setExportMetaList])
|
||||
|
||||
return (
|
||||
<SettingContext.Provider
|
||||
value={{
|
||||
format,
|
||||
setFormat,
|
||||
|
||||
enableTimestamp,
|
||||
setEnableTimestamp,
|
||||
timeStamp24H,
|
||||
setTimeStamp24H,
|
||||
|
||||
enableMeta,
|
||||
setEnableMeta,
|
||||
exportMetaList,
|
||||
setExportMetaList,
|
||||
|
||||
resetDefault,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</SettingContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useSettingContext = () => useContext(SettingContext)
|
||||
@@ -5,10 +5,9 @@ import { useTitle } from '../hooks/useTitle'
|
||||
import { getChatIdFromUrl } from '../page'
|
||||
import { getFileNameWithFormat } from '../utils/download'
|
||||
import { timestamp as _timestamp, dateStr } from '../utils/utils'
|
||||
import { CheckBox } from './CheckBox'
|
||||
import { useFormatContext } from './FormatContext'
|
||||
import { IconCross, IconTrash } from './Icons'
|
||||
import { useMetaDataContext } from './MetaContext'
|
||||
import { useSettingContext } from './SettingContext'
|
||||
import { Toggle } from './Toggle'
|
||||
import type { FC } from '../type'
|
||||
|
||||
const Variable = ({ name, title }: { name: string; title: string }) => (
|
||||
@@ -25,9 +24,13 @@ export const SettingDialog: FC<SettingDialogProps> = ({
|
||||
onOpenChange,
|
||||
children,
|
||||
}) => {
|
||||
const { format, setFormat } = useFormatContext()
|
||||
const { enableMeta, setEnableMeta, exportMetaList, setExportMetaList } = useMetaDataContext()
|
||||
|
||||
const {
|
||||
format, setFormat,
|
||||
enableTimestamp, setEnableTimestamp,
|
||||
timeStamp24H, setTimeStamp24H,
|
||||
enableMeta, setEnableMeta,
|
||||
exportMetaList, setExportMetaList,
|
||||
} = useSettingContext()
|
||||
const _title = useTitle()
|
||||
const date = dateStr()
|
||||
const timestamp = _timestamp()
|
||||
@@ -51,101 +54,122 @@ export const SettingDialog: FC<SettingDialogProps> = ({
|
||||
<Dialog.Title className="DialogTitle">Exporter Setting</Dialog.Title>
|
||||
|
||||
<dl className="space-y-6">
|
||||
<div>
|
||||
<dt>
|
||||
<label className="text-sm font-medium text-gray-800 dark:text-white" htmlFor="filename">
|
||||
<div className="relative flex bg-white dark:bg-white/5 rounded p-4">
|
||||
<div>
|
||||
<dt className="text-md font-medium text-gray-800 dark:text-white">
|
||||
File Name
|
||||
</label>
|
||||
</dt>
|
||||
<dd>
|
||||
<p className="mt-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
Available variables:
|
||||
<Variable name="{title}" title={title} />
|
||||
,
|
||||
<Variable name="{date}" title={date} />
|
||||
,
|
||||
<Variable name="{timestamp}" title={timestamp} />
|
||||
,
|
||||
<Variable name="{chat_id}" title={chatId} />
|
||||
</p>
|
||||
<input className="Input mt-1" id="filename" value={format} onChange={e => setFormat(e.currentTarget.value)} />
|
||||
<p className="mt-1 text-sm text-gray-700 dark:text-gray-300">
|
||||
Preview:
|
||||
<span className="select-all" style={{ 'text-decoration': 'underline', 'text-underline-offset': 4 }}>{preview}</span>
|
||||
</p>
|
||||
</dd>
|
||||
</dt>
|
||||
<dd>
|
||||
<p className="text-sm text-gray-700 dark:text-gray-300">
|
||||
Available variables:{' '}
|
||||
<Variable name="{title}" title={title} />
|
||||
,{' '}
|
||||
<Variable name="{date}" title={date} />
|
||||
,{' '}
|
||||
<Variable name="{timestamp}" title={timestamp} />
|
||||
,{' '}
|
||||
<Variable name="{chat_id}" title={chatId} />
|
||||
</p>
|
||||
<input className="Input mt-4" id="filename" value={format} onChange={e => setFormat(e.currentTarget.value)} />
|
||||
<p className="mt-1 text-sm text-gray-700 dark:text-gray-300">
|
||||
Preview:{' '}
|
||||
<span className="select-all" style={{ 'text-decoration': 'underline', 'text-underline-offset': 4 }}>{preview}</span>
|
||||
</p>
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<dt>
|
||||
<div className="text-sm font-medium text-gray-800 dark:text-white">
|
||||
<div className="relative flex bg-white dark:bg-white/5 rounded p-4">
|
||||
<div>
|
||||
<dt className="text-md font-medium text-gray-800 dark:text-white">
|
||||
Conversation Timestamp
|
||||
</dt>
|
||||
<dd className="text-sm text-gray-700 dark:text-gray-300">
|
||||
Will show on the page and HTML files.
|
||||
{enableTimestamp && (
|
||||
<div className="mt-2">
|
||||
<Toggle
|
||||
label="Use 24-hour format (eg. 23:59)"
|
||||
checked={timeStamp24H}
|
||||
onCheckedUpdate={setTimeStamp24H}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="absolute right-4">
|
||||
<Toggle label="" checked={enableTimestamp} onCheckedUpdate={setEnableTimestamp} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex bg-white dark:bg-white/5 rounded p-4">
|
||||
<div>
|
||||
<dt className="text-md font-medium text-gray-800 dark:text-white">
|
||||
Export Metadata
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-gray-700 dark:text-gray-300">
|
||||
</dt>
|
||||
<dd className="text-sm text-gray-700 dark:text-gray-300">
|
||||
Add metadata to exported Markdown and HTML files.
|
||||
</p>
|
||||
</dt>
|
||||
<dd>
|
||||
<CheckBox label="Enable" className="mt-2" checked={enableMeta} onCheckedChange={setEnableMeta} />
|
||||
|
||||
{enableMeta && (
|
||||
<>
|
||||
<p className="mt-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
Available variables:
|
||||
<Variable name="{title}" title={title} />
|
||||
,
|
||||
<Variable name="{date}" title={date} />
|
||||
,
|
||||
<Variable name="{timestamp}" title={timestamp} />
|
||||
,
|
||||
<Variable name="{source}" title={source} />
|
||||
,
|
||||
<br />
|
||||
<Variable name="{model}" title={'ChatGPT-3.5'} />
|
||||
,
|
||||
<Variable name="{model_name}" title={'text-davinci-002-render-sha'} />
|
||||
</p>
|
||||
{exportMetaList.map((meta, i) => (
|
||||
<div className="flex items-center mt-2" key={i}>
|
||||
<input
|
||||
className="Input"
|
||||
value={meta.name}
|
||||
onChange={(e) => {
|
||||
const list = [...exportMetaList]
|
||||
list[i] = { ...list[i], name: e.currentTarget.value }
|
||||
setExportMetaList(list)
|
||||
}}
|
||||
/>
|
||||
<span className="mx-2">→</span>
|
||||
<input
|
||||
className="Input"
|
||||
value={meta.value}
|
||||
onChange={(e) => {
|
||||
const list = [...exportMetaList]
|
||||
list[i] = { ...list[i], value: e.currentTarget.value }
|
||||
setExportMetaList(list)
|
||||
}}
|
||||
/>
|
||||
{enableMeta && (
|
||||
<>
|
||||
<p className="mt-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
Available variables:{' '}
|
||||
<Variable name="{title}" title={title} />
|
||||
,{' '}
|
||||
<Variable name="{date}" title={date} />
|
||||
,{' '}
|
||||
<Variable name="{timestamp}" title={timestamp} />
|
||||
,{' '}
|
||||
<Variable name="{source}" title={source} />
|
||||
,{' '}
|
||||
<Variable name="{model}" title={'ChatGPT-3.5'} />
|
||||
,{' '}
|
||||
<Variable name="{model_name}" title={'text-davinci-002-render-sha'} />
|
||||
</p>
|
||||
{exportMetaList.map((meta, i) => (
|
||||
<div className="flex items-center mt-2" key={i}>
|
||||
<input
|
||||
className="Input"
|
||||
value={meta.name}
|
||||
onChange={(e) => {
|
||||
const list = [...exportMetaList]
|
||||
list[i] = { ...list[i], name: e.currentTarget.value }
|
||||
setExportMetaList(list)
|
||||
}}
|
||||
/>
|
||||
<span className="mx-2">→</span>
|
||||
<input
|
||||
className="Input"
|
||||
value={meta.value}
|
||||
onChange={(e) => {
|
||||
const list = [...exportMetaList]
|
||||
list[i] = { ...list[i], value: e.currentTarget.value }
|
||||
setExportMetaList(list)
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
className="ml-2 rounded-full p-1 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition ease-in-out duration-150"
|
||||
aria-label="Remove"
|
||||
onClick={() => setExportMetaList(exportMetaList.filter((_, j) => j !== i))}
|
||||
>
|
||||
<IconTrash className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex justify-center items-center mt-2 pr-8">
|
||||
<button
|
||||
className="ml-2 rounded-full p-1 text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition ease-in-out duration-150"
|
||||
aria-label="Remove"
|
||||
onClick={() => setExportMetaList(exportMetaList.filter((_, j) => j !== i))}
|
||||
className="w-full border border-[#6f6e77] dark:border-gray-[#86858d] rounded-md py-2 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition ease-in-out duration-150"
|
||||
aria-label="Add"
|
||||
onClick={() => setExportMetaList([...exportMetaList, { name: '', value: '' }])}
|
||||
>
|
||||
<IconTrash className="w-4 h-4" />
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex justify-center items-center mt-2 pr-8">
|
||||
<button
|
||||
className="w-full border border-[#6f6e77] dark:border-gray-[#86858d] rounded-md py-2 text-sm font-medium text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 transition ease-in-out duration-150"
|
||||
aria-label="Add"
|
||||
onClick={() => setExportMetaList([...exportMetaList, { name: '', value: '' }])}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</dd>
|
||||
</>
|
||||
)}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="absolute right-4">
|
||||
<Toggle label="" checked={enableMeta} onCheckedUpdate={setEnableMeta} />
|
||||
</div>
|
||||
</div>
|
||||
</dl>
|
||||
<div className="flex mt-6" style={{ justifyContent: 'flex-end' }}>
|
||||
|
||||
25
packages/userscript/src/ui/Toggle.tsx
Normal file
25
packages/userscript/src/ui/Toggle.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
interface ToggleProps {
|
||||
label?: string
|
||||
checked?: boolean
|
||||
onCheckedUpdate?: (checked: boolean) => void
|
||||
}
|
||||
|
||||
export const Toggle = (props: ToggleProps) => {
|
||||
const { label = '', checked = true, onCheckedUpdate } = props
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!e.target || !onCheckedUpdate) return
|
||||
onCheckedUpdate((e.target as HTMLInputElement).checked)
|
||||
}
|
||||
return (
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={handleChange}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-9 h-5 bg-gray-200 peer-focus:outline-none rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
|
||||
<span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300 empty:hidden">{label}</span>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
75
pnpm-lock.yaml
generated
75
pnpm-lock.yaml
generated
@@ -28,7 +28,7 @@ importers:
|
||||
cpy-cli: 4.2.0
|
||||
eslint: 8.37.0
|
||||
husky: 8.0.3
|
||||
lint-staged: 13.2.0
|
||||
lint-staged: 13.2.1
|
||||
rimraf: 4.4.1
|
||||
turbo: 1.8.8
|
||||
typescript: 5.0.3
|
||||
@@ -86,12 +86,12 @@ importers:
|
||||
|
||||
packages:
|
||||
|
||||
/@ampproject/remapping/2.2.0:
|
||||
resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==}
|
||||
/@ampproject/remapping/2.2.1:
|
||||
resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.1.1
|
||||
'@jridgewell/trace-mapping': 0.3.17
|
||||
'@jridgewell/gen-mapping': 0.3.3
|
||||
'@jridgewell/trace-mapping': 0.3.18
|
||||
dev: true
|
||||
|
||||
/@babel/code-frame/7.21.4:
|
||||
@@ -110,7 +110,7 @@ packages:
|
||||
resolution: {integrity: sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.2.0
|
||||
'@ampproject/remapping': 2.2.1
|
||||
'@babel/code-frame': 7.21.4
|
||||
'@babel/generator': 7.21.4
|
||||
'@babel/helper-compilation-targets': 7.21.4_@babel+core@7.21.4
|
||||
@@ -134,8 +134,8 @@ packages:
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/types': 7.21.4
|
||||
'@jridgewell/gen-mapping': 0.3.2
|
||||
'@jridgewell/trace-mapping': 0.3.17
|
||||
'@jridgewell/gen-mapping': 0.3.3
|
||||
'@jridgewell/trace-mapping': 0.3.18
|
||||
jsesc: 2.5.2
|
||||
dev: true
|
||||
|
||||
@@ -774,21 +774,13 @@ packages:
|
||||
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/gen-mapping/0.1.1:
|
||||
resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
|
||||
/@jridgewell/gen-mapping/0.3.3:
|
||||
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
dev: true
|
||||
|
||||
/@jridgewell/gen-mapping/0.3.2:
|
||||
resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
'@jridgewell/trace-mapping': 0.3.17
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
'@jridgewell/trace-mapping': 0.3.18
|
||||
dev: true
|
||||
|
||||
/@jridgewell/resolve-uri/3.1.0:
|
||||
@@ -796,6 +788,11 @@ packages:
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/resolve-uri/3.1.1:
|
||||
resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/set-array/1.1.2:
|
||||
resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
@@ -805,8 +802,12 @@ packages:
|
||||
resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping/0.3.17:
|
||||
resolution: {integrity: sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==}
|
||||
/@jridgewell/sourcemap-codec/1.4.15:
|
||||
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
|
||||
dev: true
|
||||
|
||||
/@jridgewell/trace-mapping/0.3.18:
|
||||
resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.0
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
@@ -815,8 +816,8 @@ packages:
|
||||
/@jridgewell/trace-mapping/0.3.9:
|
||||
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.0
|
||||
'@jridgewell/sourcemap-codec': 1.4.14
|
||||
'@jridgewell/resolve-uri': 3.1.1
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
dev: true
|
||||
|
||||
/@nodelib/fs.scandir/2.1.5:
|
||||
@@ -918,7 +919,7 @@ packages:
|
||||
babel-plugin-transform-hook-names: 1.0.2_@babel+core@7.21.4
|
||||
debug: 4.3.4
|
||||
kolorist: 1.7.0
|
||||
resolve: 1.22.1
|
||||
resolve: 1.22.2
|
||||
vite: 4.2.1
|
||||
transitivePeerDependencies:
|
||||
- preact
|
||||
@@ -1673,7 +1674,7 @@ packages:
|
||||
hasBin: true
|
||||
dependencies:
|
||||
caniuse-lite: 1.0.30001474
|
||||
electron-to-chromium: 1.4.352
|
||||
electron-to-chromium: 1.4.355
|
||||
node-releases: 2.0.10
|
||||
update-browserslist-db: 1.0.10_browserslist@4.21.5
|
||||
dev: true
|
||||
@@ -2151,8 +2152,8 @@ packages:
|
||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||
dev: true
|
||||
|
||||
/electron-to-chromium/1.4.352:
|
||||
resolution: {integrity: sha512-ikFUEyu5/q+wJpMOxWxTaEVk2M1qKqTGKKyfJmod1CPZxKfYnxVS41/GCBQg21ItBpZybyN8sNpRqCUGm+Zc4Q==}
|
||||
/electron-to-chromium/1.4.355:
|
||||
resolution: {integrity: sha512-056hxzEE4l667YeOccgjhRr5fTiwZ6EIJ4FpzGps4k3YcS8iAhiaBYUBrv5E2LDQJsussscv9EEUwAYKnv+ZKg==}
|
||||
dev: true
|
||||
|
||||
/emoji-regex/8.0.0:
|
||||
@@ -2290,7 +2291,7 @@ packages:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
is-core-module: 2.11.0
|
||||
resolve: 1.22.1
|
||||
resolve: 1.22.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: true
|
||||
@@ -2376,7 +2377,7 @@ packages:
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.1.2
|
||||
object.values: 1.1.6
|
||||
resolve: 1.22.1
|
||||
resolve: 1.22.2
|
||||
semver: 6.3.0
|
||||
tsconfig-paths: 3.14.2
|
||||
transitivePeerDependencies:
|
||||
@@ -2443,7 +2444,7 @@ packages:
|
||||
ignore: 5.2.4
|
||||
is-core-module: 2.11.0
|
||||
minimatch: 3.1.2
|
||||
resolve: 1.22.1
|
||||
resolve: 1.22.2
|
||||
semver: 7.3.8
|
||||
dev: true
|
||||
|
||||
@@ -3596,8 +3597,8 @@ packages:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
dev: true
|
||||
|
||||
/lint-staged/13.2.0:
|
||||
resolution: {integrity: sha512-GbyK5iWinax5Dfw5obm2g2ccUiZXNGtAS4mCbJ0Lv4rq6iEtfBSjOYdcbOtAIFtM114t0vdpViDDetjVTSd8Vw==}
|
||||
/lint-staged/13.2.1:
|
||||
resolution: {integrity: sha512-8gfzinVXoPfga5Dz/ZOn8I2GOhf81Wvs+KwbEXQn/oWZAvCVS2PivrXfVbFJc93zD16uC0neS47RXHIjXKYZQw==}
|
||||
engines: {node: ^14.13.1 || >=16.0.0}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
@@ -4326,7 +4327,7 @@ packages:
|
||||
resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==}
|
||||
dependencies:
|
||||
hosted-git-info: 2.8.9
|
||||
resolve: 1.22.1
|
||||
resolve: 1.22.2
|
||||
semver: 5.7.1
|
||||
validate-npm-package-license: 3.0.4
|
||||
dev: true
|
||||
@@ -4880,8 +4881,8 @@ packages:
|
||||
global-dirs: 0.1.1
|
||||
dev: true
|
||||
|
||||
/resolve/1.22.1:
|
||||
resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
|
||||
/resolve/1.22.2:
|
||||
resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
is-core-module: 2.11.0
|
||||
@@ -5704,7 +5705,7 @@ packages:
|
||||
dependencies:
|
||||
esbuild: 0.17.15
|
||||
postcss: 8.4.21
|
||||
resolve: 1.22.1
|
||||
resolve: 1.22.2
|
||||
rollup: 3.20.2
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
Reference in New Issue
Block a user