Skip to content

Commit

Permalink
feat: add conversation timestamp on page and HTML, revamp setting UI
Browse files Browse the repository at this point in the history
closes #54, closes #104
  • Loading branch information
pionxzh committed Apr 7, 2023
1 parent 41fd500 commit 531a766
Show file tree
Hide file tree
Showing 15 changed files with 449 additions and 224 deletions.
2 changes: 2 additions & 0 deletions packages/userscript/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
18 changes: 12 additions & 6 deletions packages/userscript/src/exporter/html.ts
Original file line number Diff line number Diff line change
@@ -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()) {
Expand Down Expand Up @@ -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 `
Expand All @@ -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')

Expand Down
2 changes: 1 addition & 1 deletion packages/userscript/src/exporter/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
39 changes: 38 additions & 1 deletion packages/userscript/src/main.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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)
})
})
})
}
12 changes: 12 additions & 0 deletions packages/userscript/src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
111 changes: 103 additions & 8 deletions packages/userscript/src/styles/missing-tailwind.css
Original file line number Diff line number Diff line change
@@ -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;
}
Expand All @@ -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));
Expand All @@ -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 {
Expand All @@ -45,6 +108,14 @@
height: 0.625rem;
}

.h-4 {
height: 1rem;
}

.h-5 {
height: 1.25rem;
}

.mr-8 {
margin-right: 2rem;
}
Expand All @@ -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;
}
Expand All @@ -77,6 +168,10 @@
white-space: nowrap;
}

.w-9 {
width: 2.25rem;
}

.whitespace-nowrap {
white-space: nowrap;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/userscript/src/ui/Dialog.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
}

.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;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90vw;
max-width: 450px;
max-width: 560px;
max-height: 85vh;
overflow-x: hidden;
overflow-y: auto;
Expand Down
4 changes: 2 additions & 2 deletions packages/userscript/src/ui/ExportDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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[]>([])
Expand Down
25 changes: 0 additions & 25 deletions packages/userscript/src/ui/FormatContext.tsx

This file was deleted.

Loading

0 comments on commit 531a766

Please sign in to comment.