Skip to content

Commit

Permalink
Merge pull request #39 from jeffnawroth/development
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffnawroth authored Nov 12, 2024
2 parents 7eba2db + 2deb189 commit eebb93d
Show file tree
Hide file tree
Showing 16 changed files with 118 additions and 84 deletions.
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@
![MIT License](https://img.shields.io/badge/License-MIT-green.svg) ![GitHub Release](https://img.shields.io/github/v/release/jeffnawroth/source-taster) ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/jeffnawroth/source-taster/ci.yml)

# The Source Taster

The Source Taster is a browser extension that helps users, especially students and researchers, quickly and efficiently verify the validity and existence of sources cited in academic papers.

## Demo

<p align="center">
<img src="https://github.com/user-attachments/assets/900dd96c-7b5e-4bae-946f-59d15f8a80eb" >
</p>

## Features

- **📥 Context Menu Import**: Load bibliographies directly via the context menu into the extension
- **🔍 Automatic Detection**: Automatically detect and load DOIs from websites
- **📎 PDF Import**: Import PDF files directly into the extension and validate their DOIs
Expand All @@ -35,13 +32,11 @@ The Source Taster functions by extracting DOIs from the text of academic papers.
https://api.crossref.org/swagger-ui/index.html -->

## Acknowledgements

[WebExtension Vite Starter](https://github.com/antfu-collective/vitesse-webext) - A Vite powered WebExtension starter template

## Authors

- [@jeffnawroth](https://www.github.com/jeffnawroth)
- [@ErenC61](https://www.github.com/erenc61)
## Disclaimer
> [!WARNING]
**Disclaimer:** The Source Taster extension is a helpful tool for verifying the existence and validity of DOIs. While we strive to provide accurate results, the automated checks cannot guarantee complete accuracy. We recommend manually verifying all sources, especially for critical research. Do not solely rely on the results from this extension—conduct additional checks to ensure reliability.

## License
[MIT](/LICENSE)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "source-taster",
"displayName": "The Source Taster",
"version": "1.4.2",
"version": "1.4.3",
"private": true,
"description": "Source Taster is a browser extension that helps users quickly verify the validity and existence of sources cited in academic papers.",
"scripts": {
Expand Down
1 change: 0 additions & 1 deletion src/.env

This file was deleted.

90 changes: 55 additions & 35 deletions src/background/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { onMessage, sendMessage } from 'webext-bridge/background'
import { getDisplayOption } from '~/logic/storage'

// import type { Tabs } from 'webextension-polyfill'

// only on dev mode
Expand All @@ -14,6 +13,8 @@ if (import.meta.hot) {
declare let chrome: any
const USE_SIDE_PANEL = true
let cachedDisplayOption: string = 'sidepanel'
let isSidePanelOpen = false // Track Sidepanel open status
let currentLocale: string

// to toggle the sidepanel with the action button in chromium:
if (USE_SIDE_PANEL) {
Expand All @@ -40,8 +41,30 @@ browser.runtime.onInstalled.addListener((): void => {
title: chrome.i18n.getMessage('openSidePanel'),
contexts: ['all'],
})

// Update context menu state initially
updateContextMenuState()
})

// Call this function initially after setting the locale to set the language correctly
async function updateContextMenuState() {
const translations = await getTranslations(currentLocale || 'en')
const title = translations.openSidePanel.message
// eslint-disable-next-line no-console
console.log('Setting menu title for openSidePanel:', title)
browser.contextMenus.update('openSidePanel', {
title, // Dynamically set the title based on the language
enabled: !isSidePanelOpen,
}).catch((error) => {
if (error.message.includes('Cannot find menu item with id openSidePanel')) {
console.warn('Context menu item "openSidePanel" not found. It might not be created yet.')
}
else {
console.warn('Failed to update context menu item:', error)
}
})
}

// Global error-catcher for unhandled Promise rejections
// eslint-disable-next-line no-restricted-globals
self.addEventListener('unhandledrejection', (event) => {
Expand All @@ -51,47 +74,31 @@ self.addEventListener('unhandledrejection', (event) => {
}
})

// Helper function to open the sidepanel with specific validation and fallback
// Function to open Sidepanel and update state
// @ts-expect-error missing types
function attemptSidePanelOpen(windowId: number | null, selectedText?: string, tab?: chrome.tabs.Tab) {
if (windowId !== null && windowId !== -1) {
// eslint-disable-next-line no-console
console.log(`Performing sidepanel open with validated windowId: ${windowId}`)

try {
// @ts-expect-error missing types
browser.sidePanel.open({ windowId }).then(() => {
// eslint-disable-next-line no-console
console.log('Sidepanel opened with validated `windowId`...')
if (selectedText) {
sendMessage('bibliography', { selectedText }, { context: 'popup', tabId: tab!.id! })
}
}).catch((error: any) => {
console.error('Failed to open sidepanel due to unexpected error:', error)
})
}
catch (error) {
console.error('Error during sidepanel open attempt:', error)
}
}
else {
console.warn('Invalid `windowId`, fetching last focused window...')
chrome.windows.getLastFocused((lastFocusedWindow: { id: number | null }) => {
if (lastFocusedWindow && lastFocusedWindow.id && lastFocusedWindow.id !== -1) {
// eslint-disable-next-line no-console
console.log(`Fallback windowId obtained from last focused window: ${lastFocusedWindow.id}`)
attemptSidePanelOpen(lastFocusedWindow.id, selectedText, tab) // Retry with valid `windowId`
}
else {
console.error('No valid windowId available for sidepanel open.')
// @ts-expect-error missing types
browser.sidePanel.open({ windowId }).then(() => {
isSidePanelOpen = true
updateContextMenuState()
// eslint-disable-next-line no-console
console.log('Sidepanel opened with validated `windowId`...')
if (selectedText && tab) {
sendMessage('bibliography', { selectedText }, { context: 'popup', tabId: tab.id! })
}
}).catch((error: any) => {
console.error('Failed to open sidepanel:', error)
})
}
}

// Context menu listener for `check-bibliography` and `openSidePanel` items separately
// Handle context menu clicks
browser.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'check-bibliography' && info.selectionText) {
// Check display option and open appropriate UI
if (cachedDisplayOption === 'popup') {
// eslint-disable-next-line no-console
console.log('Opening popup...')
Expand All @@ -104,12 +111,12 @@ browser.contextMenus.onClicked.addListener((info, tab) => {
}
else if (cachedDisplayOption === 'sidepanel') {
// @ts-expect-error missing types
attemptSidePanelOpen(tab?.windowId, info.selectionText, tab) // Uses fallback logic for sidepanel opening
attemptSidePanelOpen(tab?.windowId, info.selectionText, tab)
}
}
else if (info.menuItemId === 'openSidePanel') {
else if (info.menuItemId === 'openSidePanel' && !isSidePanelOpen) {
// @ts-expect-error missing types
attemptSidePanelOpen(tab?.windowId) // Applies fallback logic specifically for openSidePanel
attemptSidePanelOpen(tab?.windowId)
}
})

Expand Down Expand Up @@ -156,8 +163,6 @@ getDisplayOption().then((option) => {
console.error('Failed to load display option:', error)
})

let currentLocale: string

// Listen for changes in chrome.storage.sync and update the view based on the new selection
chrome.storage.onChanged.addListener((changes: { displayOption?: { newValue: string } }, area: string) => {
// eslint-disable-next-line no-console
Expand Down Expand Up @@ -228,3 +233,18 @@ async function getTranslations(locale: string) {
console.error('Failed to fetch translations:', error)
}
}

// Listen for messages from the sidepanel about visibility changes
// @ts-expect-error missing types
browser.runtime.onMessage.addListener((message) => {
// @ts-expect-error missing types
if (message.type === 'SIDE_PANEL_CLOSED') {
isSidePanelOpen = false
updateContextMenuState()
}
// @ts-expect-error missing types
else if (message.type === 'SIDE_PANEL_OPENED') {
isSidePanelOpen = true
updateContextMenuState()
}
})
6 changes: 3 additions & 3 deletions src/components/Options/OptionCategories/About.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
// I18n
const { t } = useI18n()
const appVersion = import.meta.env.VITE_APP_VERSION
const version = `v${__APP_VERSION__}`
// Data
const items = [
{
title: t('version'),
subtitle: appVersion,
href: `https://github.com/jeffnawroth/source-taster/releases/tag/${appVersion}`,
subtitle: version,
href: `https://github.com/jeffnawroth/source-taster/releases/tag/${version}`,
appendIcon: 'mdi-open-in-new',
prependIcon: 'mdi-tag-outline',
},
Expand Down
8 changes: 4 additions & 4 deletions src/components/Report/ReportCardItem/ReportPdfDownload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import { generatePDFReport } from '../../../utils/pdfUtils'
// Work Store
// DOI Store
const { dois, works, passed, failed, warning } = storeToRefs(useDoiStore())
const { dois, works, valid, invalid, incomplete } = storeToRefs(useDoiStore())
// I18n
const { t } = useI18n()
async function downloadPDF() {
const pdfBytes = await generatePDFReport(
dois.value,
passed.value,
warning.value,
failed.value,
valid.value,
incomplete.value,
invalid.value,
works.value,
)
Expand Down
14 changes: 7 additions & 7 deletions src/components/Report/ReportCardItem/ReportSubtitle.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { useDoiStore } from '~/stores/doi'
// App Store
const { passed, warning, failed, found } = storeToRefs(useDoiStore())
const { valid, incomplete, invalid, found } = storeToRefs(useDoiStore())
// I18n
const { t } = useI18n()
Expand All @@ -16,21 +16,21 @@ const { t } = useI18n()
</span>
<span
class=" mx-1"
:class="passed > 0 ? 'text-success' : ''"
:class="valid > 0 ? 'text-success' : ''"
>
{{ `${t('passed')}: ${passed}` }}
{{ `${t('valid')}: ${valid}` }}
</span>
<span
class="mx-1"
:class="warning > 0 ? 'text-warning' : ''"
:class="incomplete > 0 ? 'text-warning' : ''"
>
{{ `${t('warning')}: ${warning}` }}
{{ `${t('incomplete')}: ${incomplete}` }}
</span>
<span
class="mx-1"
:class="failed > 0 ? 'text-error' : ''"
:class="invalid > 0 ? 'text-error' : ''"
>
{{ `${t('failed')}: ${failed}` }}
{{ `${t('invalid')}: ${invalid}` }}
</span>
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ declare const __DEV__: boolean
/** Extension name, defined in packageJson.name */
declare const __NAME__: string

declare const __APP_VERSION__: string

declare module '*.vue' {
const component: any
export default component
Expand Down
6 changes: 3 additions & 3 deletions src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
"language": "Sprache",
"language-option-description": "Wählen Sie die Sprache der Erweiterung",
"found": "Gefunden",
"passed": "Bestanden",
"warning": "Warnung",
"failed": "Fehlgeschlagen",
"valid": "Gültig",
"incomplete": "Unvollständig",
"invalid": "Ungültig",
"report": "Bericht",
"copy-doi": "DOI kopieren",
"doi-copied": "DOI kopiert",
Expand Down
6 changes: 3 additions & 3 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
"language": "Language",
"language-option-description": "Select the language of the extension",
"found": "Found",
"passed": "Passed",
"warning": "Warning",
"failed": "Failed",
"valid": "Valid",
"incomplete": "Incomplete",
"invalid": "Invalid",
"report": "Report",
"copy-doi": "Copy DOI",
"doi-copied": "DOI copied",
Expand Down
14 changes: 14 additions & 0 deletions src/logic/sidepanelVisibilityListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// sidepanelVisibilityListener.ts
export function setupSidepanelVisibilityListener() {
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
// Send a message to the background process when the sidepanel is hidden or closed
// @ts-expect-error missing types
chrome.runtime.sendMessage({ type: 'SIDE_PANEL_CLOSED' })
}
else if (document.visibilityState === 'visible') {
// @ts-expect-error missing types
chrome.runtime.sendMessage({ type: 'SIDE_PANEL_OPENED' })
}
})
}
3 changes: 3 additions & 0 deletions src/sidepanel/main.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { createApp } from 'vue'

import { setupApp } from '~/logic/common-setup'
import { setupSidepanelVisibilityListener } from '~/logic/sidepanelVisibilityListener'
import i18n from '~/plugins/i18n'
import pinia from '~/plugins/pinia'
import vuetify from '~/plugins/vuetify'

import App from './Sidepanel.vue'

setupSidepanelVisibilityListener()

const app = createApp(App).use(vuetify).use(i18n).use(pinia)
setupApp(app)
app.mount('#app')
10 changes: 5 additions & 5 deletions src/stores/doi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ export const useDoiStore = defineStore('doi', () => {
const found = computed(() => works.value.length)

// Number of DOIs that passed the check
const passed = computed(() => works.value.filter(work => work.ok && work.status === 200 && work.content).length)
const valid = computed(() => works.value.filter(work => work.ok && work.status === 200 && work.content).length)

// Number of DOIs that have a warning
const warning = computed(() => works.value.filter(work => work.ok && work.status === 200 && !work.content).length)
// Number of DOIs that passed the check but have no content
const incomplete = computed(() => works.value.filter(work => work.ok && work.status === 200 && !work.content).length)

// Number of DOIs that failed the check
const failed = computed(() => works.value.filter(work => !work.ok && work.status === 404).length)
const invalid = computed(() => works.value.filter(work => !work.ok && work.status === 404).length)

// Resolves the DOI
async function resolveDOI(doi: string) {
Expand Down Expand Up @@ -100,7 +100,7 @@ export const useDoiStore = defineStore('doi', () => {
loading.value = false
}

return { dois, resolveDOI, bibliography, loading, loadAborted, works, found, passed, warning, failed, getDOIsMetadata, abortFetching, url, file }
return { dois, resolveDOI, bibliography, loading, loadAborted, works, found, valid, incomplete, invalid, getDOIsMetadata, abortFetching, url, file }
})

if (import.meta.hot) {
Expand Down
Loading

0 comments on commit eebb93d

Please sign in to comment.