├── .browserslistrc
├── .editorconfig
├── .env
├── .env.development
├── .env.production
├── .eslintrc.js
├── .github
└── workflows
│ ├── codeql.yml
│ ├── dependency-review.yml
│ └── eslint.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── CODE_OF_CONDUCT.md
├── LICENSE
├── Makefile
├── README.md
├── babel.config.js
├── jest.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ ├── images
│ │ ├── android-chrome-192x192.png
│ │ ├── android-chrome-512x512.png
│ │ ├── apple-touch-icon.png
│ │ ├── connector-graph.png
│ │ ├── crossref-logo.jpg
│ │ ├── einstein-tounge.jpg
│ │ ├── einstein.jpg
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── gitlab.svg
│ │ ├── graph-purple.png
│ │ ├── graph.png
│ │ ├── linkedin.svg
│ │ ├── logo-200.png
│ │ ├── logo-300.png
│ │ ├── logo.png
│ │ ├── network-purple.png
│ │ ├── openalex-logo.png
│ │ ├── opencitations-logo.png
│ │ ├── profile-transparent.png
│ │ ├── reddit.svg
│ │ ├── semantic-scholar-logo.png
│ │ ├── svgs.svg
│ │ ├── twitter.svg
│ │ └── unpaywall-logo.jpg
│ └── tailwind.css
├── components
│ ├── AbstractView.vue
│ ├── Author.vue
│ ├── Authors.vue
│ ├── Autosuggest.vue
│ ├── BetaFeatures.vue
│ ├── BetaSignup.vue
│ ├── ConnectorSearch.vue
│ ├── ConnectorTable.vue
│ ├── DashboardRenderer.vue
│ ├── ExternalLinks.vue
│ ├── Faq.vue
│ ├── FavoritePaperButton.vue
│ ├── FavoritePapers.vue
│ ├── GraphFilters.vue
│ ├── GraphSearch.vue
│ ├── GraphView.vue
│ ├── LitConnectorBody.vue
│ ├── LitConnectorPaperSelector.vue
│ ├── LitConnectorTour.vue
│ ├── LitReviewBuilder.vue
│ ├── LitReviewButton.vue
│ ├── LitReviewHero.vue
│ ├── Loader.vue
│ ├── PaperDiscoveryEmpty.vue
│ ├── PaperHero.vue
│ ├── PaperHeroStats.vue
│ ├── PaperList.vue
│ ├── PaperPageTour.vue
│ ├── PaperSummary.vue
│ ├── QueryPanel.vue
│ ├── SaveDropDown.vue
│ ├── SearchResults.vue
│ ├── SimilarGraph.vue
│ ├── SqlView.vue
│ ├── Stat.vue
│ ├── StatView.vue
│ ├── TableBase.vue
│ ├── TableView.vue
│ ├── Tour.vue
│ ├── announcements
│ │ ├── AnnouncementBanner.vue
│ │ ├── MedSurveyAnnouncement.vue
│ │ └── ZoteroAnnouncement.vue
│ ├── layout
│ │ ├── Footer.vue
│ │ ├── Header.vue
│ │ └── SingleColumn.vue
│ └── modals
│ │ ├── AuthorModalContent.vue
│ │ ├── ModalManager.vue
│ │ ├── NotificationModal.vue
│ │ ├── PaperModalButton.vue
│ │ └── PaperModalContent.vue
├── dashboard_templates
│ ├── default_lr_template.yaml
│ └── default_paper_template.yaml
├── main.ts
├── navigation.ts
├── router
│ └── index.ts
├── shim-declarations.d.ts
├── shim-tsx.d.ts
├── shim-vue.d.ts
├── stores
│ └── userStore.ts
├── types
│ ├── graphTypes.ts
│ ├── incitefulTypes.ts
│ ├── modalTypes.ts
│ ├── openAlexTypes.ts
│ ├── userTypes.ts
│ └── yaml.ts
├── utils
│ ├── bib.ts
│ ├── doi.ts
│ ├── emitHelpers.ts
│ ├── exportUtils.ts
│ ├── graphing
│ │ ├── connector.ts
│ │ ├── graph.ts
│ │ ├── graphStyles.ts
│ │ └── similar.ts
│ ├── incitefulApi.ts
│ ├── keywords.ts
│ ├── logging.ts
│ ├── openalexApi.ts
│ ├── options.ts
│ ├── pagedata.ts
│ └── sql.ts
└── views
│ ├── About.vue
│ ├── BetaFeatures.vue
│ ├── DataSources.vue
│ ├── Einstein.vue
│ ├── Export.vue
│ ├── Home.vue
│ ├── LitConnector.vue
│ ├── LitReview.vue
│ ├── LitReviewQuery.vue
│ ├── NotFound.vue
│ ├── PaperDiscovery.vue
│ ├── PaperDiscoveryQuery.vue
│ └── Search.vue
├── tailwind.config.js
├── tests
└── unit
│ └── example.spec.ts
├── tsconfig.json
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | VUE_APP_PROJECT_ID='inciteful-xyz'
2 |
--------------------------------------------------------------------------------
/.env.development:
--------------------------------------------------------------------------------
1 | # VUE_APP_CLIENT_API_URL=http://localhost:8080
2 | VUE_APP_CLIENT_API_URL=https://api.inciteful.xyz
3 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | VUE_APP_SHOW_LOGIN=false
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | '@vue/typescript/recommended'
10 | // '@vue/standard'
11 | ],
12 | parserOptions: {
13 | parser: '@typescript-eslint/parser'
14 | },
15 | rules: {
16 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
18 | 'vue/multi-word-component-names': 'off',
19 | '@typescript-eslint/ban-ts-comment': 'off'
20 | },
21 | overrides: [
22 | {
23 | files: [
24 | '**/__tests__/*.{j,t}s?(x)',
25 | '**/tests/unit/**/*.spec.{j,t}s?(x)'
26 | ],
27 | env: {
28 | jest: true
29 | }
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/.github/workflows/codeql.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "master" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "master" ]
20 | schedule:
21 | - cron: '30 19 * * 3'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 | with:
74 | category: "/language:${{matrix.language}}"
75 |
--------------------------------------------------------------------------------
/.github/workflows/dependency-review.yml:
--------------------------------------------------------------------------------
1 | # Dependency Review Action
2 | #
3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
4 | #
5 | # Source repository: https://github.com/actions/dependency-review-action
6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
7 | name: 'Dependency Review'
8 | on: [pull_request]
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | dependency-review:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - name: 'Checkout Repository'
18 | uses: actions/checkout@v3
19 | - name: 'Dependency Review'
20 | uses: actions/dependency-review-action@v2
21 |
--------------------------------------------------------------------------------
/.github/workflows/eslint.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 | # ESLint is a tool for identifying and reporting on patterns
6 | # found in ECMAScript/JavaScript code.
7 | # More details at https://github.com/eslint/eslint
8 | # and https://eslint.org
9 |
10 | name: ESLint
11 |
12 | on:
13 | push:
14 | branches: [ "master" ]
15 | pull_request:
16 | # The branches below must be a subset of the branches above
17 | branches: [ "master" ]
18 | schedule:
19 | - cron: '38 5 * * 3'
20 |
21 | jobs:
22 | build:
23 | name: Run eslint scanning
24 | runs-on: ubuntu-latest
25 | permissions:
26 | # required for all workflows
27 | security-events: write
28 | # only required for workflows in private repositories
29 | actions: read
30 | contents: read
31 | steps:
32 | - uses: actions/checkout@v4
33 | - uses: actions/setup-node@v3
34 | with:
35 | node-version-file: '.nvmrc'
36 |
37 | - name: Install ESLint
38 | run: |
39 | npm install eslint@8.10.0
40 | npm install @microsoft/eslint-formatter-sarif@2.1.7
41 |
42 | - name: Run ESLint
43 | run: npx eslint .
44 | --config .eslintrc.js
45 | --ext .js,.jsx,.ts,.tsx
46 | --format @microsoft/eslint-formatter-sarif
47 | --output-file eslint-results.sarif
48 | continue-on-error: true
49 |
50 | - name: Upload analysis results to GitHub
51 | uses: github/codeql-action/upload-sarif@v2
52 | with:
53 | sarif_file: eslint-results.sarif
54 | wait-for-processing: true
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16.13
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | feedback@inciteful.xyz.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | npm-watch:
2 | npm run watch
3 |
4 | npm-build-release:
5 | npm install
6 | npm run build-prod
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Inciteful Web App
2 | This is the front end of [Inciteful.xyz](https://inciteful.xyz).
3 |
4 | ## Contributing
5 | This is a personal project that I intend to keep free forever. As such, there is a lot of low hanging fruit that I have yet to get to and as a result there is a lot of opportunity for people to contribute. Feel free to reach out to me: michael@inciteful.xyz.
6 |
7 | ## Project setup
8 | ```
9 | npm install
10 | ```
11 |
12 | ### Compiles and hot-reloads for development
13 | ```
14 | npm run watch
15 | ```
16 |
17 | ### Compiles and minifies for production
18 | ```
19 | npm run serve
20 | ```
21 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '@vue/cli-plugin-unit-jest'
3 | }
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "inciteful",
3 | "version": "0.1.0",
4 | "private": true,
5 | "engines": {
6 | "node": "16.x"
7 | },
8 | "scripts": {
9 | "serve": "vue-cli-service serve --port=8081",
10 | "build": "vue-cli-service build",
11 | "test:unit": "vue-cli-service test:unit",
12 | "lint": "vue-cli-service lint",
13 | "analyze-prod": "BUNDLE_ANALYZE=true npm run build-prod",
14 | "watch": "vue-cli-service build --watch"
15 | },
16 | "dependencies": {
17 | "@headlessui/vue": "^1.4.3",
18 | "@hennge/vue3-pagination": "^1.0.17",
19 | "@heroicons/vue": "^1.0.5",
20 | "@sentry/browser": "^6.15.0",
21 | "@sentry/tracing": "^6.15.0",
22 | "@sentry/vue": "^6.15.0",
23 | "@vueuse/head": "^1.1.19",
24 | "axios": "^0.21.2",
25 | "axios-retry": "^3.1.9",
26 | "bib2json": "0.0.1",
27 | "codejar": "^3.2.2",
28 | "core-js": "^3.6.5",
29 | "cytoscape": "^3.18.2",
30 | "cytoscape-context-menus": "^4.1.0",
31 | "cytoscape-fcose": "^2.0.0",
32 | "cytoscape-klay": "^3.1.4",
33 | "cytoscape-popper": "^2.0.0",
34 | "doi-regex": "^0.1.10",
35 | "flexsearch": "^0.7.11",
36 | "install": "^0.13.0",
37 | "mitt": "^3.0.0",
38 | "npm": "^8.11.0",
39 | "numeral": "^2.0.6",
40 | "pinia": "^2.0.9",
41 | "prismjs": "^1.27.0",
42 | "qs": "^6.10.3",
43 | "stemmer": "^2.0.0",
44 | "tippy.js": "^6.3.1",
45 | "v3-tour": "^3.0.16",
46 | "vue": "^3.2.0",
47 | "vue-router": "^4.0.0-alpha.6",
48 | "vue3-click-away": "^1.2.1",
49 | "vue3-touch-events": "^4.1.0",
50 | "zotero-api-client": "^0.37.2"
51 | },
52 | "devDependencies": {
53 | "@sentry/webpack-plugin": "^1.18.3",
54 | "@tailwindcss/forms": "^0.5.3",
55 | "@types/cytoscape": "^3.19.2",
56 | "@types/cytoscape-popper": "^2.0.0",
57 | "@types/doi-regex": "^0.1.0",
58 | "@types/flexsearch": "^0.7.2",
59 | "@types/numeral": "^2.0.2",
60 | "@types/prismjs": "^1.16.6",
61 | "@typescript-eslint/eslint-plugin": "^5.4.0",
62 | "@typescript-eslint/parser": "^5.4.0",
63 | "@vue/cli": "^5.0.0-rc.2",
64 | "@vue/cli-plugin-babel": "^5.0.0-rc.2",
65 | "@vue/cli-plugin-eslint": "^5.0.0-rc.2",
66 | "@vue/cli-plugin-router": "^5.0.0-rc.2",
67 | "@vue/cli-plugin-typescript": "^5.0.0-rc.2",
68 | "@vue/cli-plugin-unit-jest": "^5.0.0-rc.2",
69 | "@vue/cli-service": "^5.0.0-rc.2",
70 | "@vue/compiler-sfc": "^3.0.0-beta.1",
71 | "@vue/eslint-config-typescript": "^9.1.0",
72 | "@vue/test-utils": "^2.0.0-alpha.1",
73 | "autoprefixer": "^10.4.13",
74 | "babel-eslint": "^10.1.0",
75 | "copy-webpack-plugin": "^6.1.0",
76 | "css-loader": "^6.5.1",
77 | "eslint": "^7.32.0",
78 | "eslint-plugin-import": "^2.25.3",
79 | "eslint-plugin-node": "^11.1.0",
80 | "eslint-plugin-promise": "^5.1.0",
81 | "eslint-plugin-vue": "^8.0.3",
82 | "file-loader": "^6.1.0",
83 | "js-yaml-loader": "^1.2.2",
84 | "json-loader": "^0.5.7",
85 | "mini-css-extract-plugin": "^1.2.1",
86 | "node-sass": "^9.0.0",
87 | "postcss": "^8.4.21",
88 | "postcss-loader": "^4.2.0",
89 | "sass": "^1.35.2",
90 | "sass-loader": "^10.2.0",
91 | "style-loader": "^1.3.0",
92 | "svg-url-loader": "^6.0.0",
93 | "tailwindcss": "^3.2.7",
94 | "typescript": "^4.5.4",
95 | "vue-cli-plugin-tailwind": "~2.0.6",
96 | "vue-loader": "^15.9.3",
97 | "webpack": "^5.76.0",
98 | "webpack-bundle-analyzer": "^4.1.0",
99 | "webpack-cli": "^4.9.1",
100 | "webpack-merge": "^5.2.0"
101 | },
102 | "mode": "development"
103 | }
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <% if (process.env.NODE_ENV==='production' ) { %>
7 |
8 |
10 |
11 | <% } %>
12 |
13 |
14 |
15 |
17 |
18 |
19 |
20 | <%= htmlWebpackPlugin.options.title %>
21 |
22 |
23 |
24 |
25 |
26 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
27 | Please enable it to continue.
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
24 |
--------------------------------------------------------------------------------
/src/assets/images/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/android-chrome-192x192.png
--------------------------------------------------------------------------------
/src/assets/images/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/android-chrome-512x512.png
--------------------------------------------------------------------------------
/src/assets/images/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/assets/images/connector-graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/connector-graph.png
--------------------------------------------------------------------------------
/src/assets/images/crossref-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/crossref-logo.jpg
--------------------------------------------------------------------------------
/src/assets/images/einstein-tounge.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/einstein-tounge.jpg
--------------------------------------------------------------------------------
/src/assets/images/einstein.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/einstein.jpg
--------------------------------------------------------------------------------
/src/assets/images/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/favicon-16x16.png
--------------------------------------------------------------------------------
/src/assets/images/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/favicon-32x32.png
--------------------------------------------------------------------------------
/src/assets/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/favicon.ico
--------------------------------------------------------------------------------
/src/assets/images/gitlab.svg:
--------------------------------------------------------------------------------
1 |
7 | tanuki
8 |
9 |
--------------------------------------------------------------------------------
/src/assets/images/graph-purple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/graph-purple.png
--------------------------------------------------------------------------------
/src/assets/images/graph.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/graph.png
--------------------------------------------------------------------------------
/src/assets/images/linkedin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/logo-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/logo-200.png
--------------------------------------------------------------------------------
/src/assets/images/logo-300.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/logo-300.png
--------------------------------------------------------------------------------
/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/logo.png
--------------------------------------------------------------------------------
/src/assets/images/network-purple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/network-purple.png
--------------------------------------------------------------------------------
/src/assets/images/openalex-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/openalex-logo.png
--------------------------------------------------------------------------------
/src/assets/images/opencitations-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/opencitations-logo.png
--------------------------------------------------------------------------------
/src/assets/images/profile-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/profile-transparent.png
--------------------------------------------------------------------------------
/src/assets/images/reddit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/semantic-scholar-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/semantic-scholar-logo.png
--------------------------------------------------------------------------------
/src/assets/images/svgs.svg:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 | home
14 |
15 |
16 |
20 | search
21 |
22 |
23 |
24 |
28 | tree
29 |
30 |
31 |
32 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/assets/images/twitter.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/images/unpaywall-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/inciteful-xyz/inciteful-web/34e4727c6334e97d53cce7549d3b6b4c766b3e2f/src/assets/images/unpaywall-logo.jpg
--------------------------------------------------------------------------------
/src/assets/tailwind.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 |
3 | @tailwind components;
4 |
5 | @tailwind utilities;
6 |
7 | .base-table {
8 | @apply min-w-full divide-y divide-gray-200 table-auto text-sm text-gray-600;
9 | }
10 | .base-table thead {
11 | @apply bg-gray-50 uppercase;
12 | }
13 | .base-table th {
14 | @apply px-2 py-2 font-medium uppercase tracking-wider text-center;
15 | }
16 | .base-table tbody {
17 | @apply bg-white divide-y divide-gray-200 text-center;
18 | }
19 | .base-table tbody td {
20 | @apply px-2 py-2;
21 | }
22 | .base-button {
23 | @apply inline-flex items-center px-3 py-2 border border-transparent leading-5 font-medium rounded-md text-white focus:outline-none transition ease-in-out duration-150;
24 | }
25 | .button-gray {
26 | @apply base-button bg-gray-500 hover:bg-gray-400 focus:border-gray-600 focus:ring-gray-400;
27 | }
28 | .button-light-violet {
29 | @apply base-button bg-violet-400
30 | hover:bg-violet-300
31 | focus:border-violet-500
32 | focus:ring-violet-500;
33 | }
34 | .button-violet {
35 | @apply base-button bg-violet-600
36 | hover:bg-violet-500
37 | focus:border-violet-700
38 | focus:ring-violet-600;
39 | }
40 | h1 {
41 | @apply text-gray-800 font-bold text-lg sm:text-2xl;
42 | }
43 |
44 | h2 {
45 | @apply text-gray-800 font-bold text-base sm:text-xl;
46 | }
47 |
48 | .Pagination {
49 | @apply py-2;
50 | }
51 | .Pagination li {
52 | @apply inline;
53 | }
54 | .Pagination .Dots {
55 | @apply h-6 w-6 inline-block;
56 | }
57 | .PaginationControl .Control {
58 | @apply h-4 w-4 inline;
59 | }
60 | .PaginationControl .Control:hover {
61 | @apply cursor-pointer;
62 | }
63 | .Pagination li button {
64 | @apply p-2 py-1 border-gray-200 border rounded m-1;
65 | }
66 | .Pagination .Page-active {
67 | @apply bg-violet-500 border-violet-800 text-white;
68 | }
69 | .shadow-box {
70 | @apply shadow border-b border-gray-200 sm:rounded-lg mb-3;
71 | }
72 |
--------------------------------------------------------------------------------
/src/components/AbstractView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ truncatedAbstract }}
4 |
9 | more less
14 |
15 |
16 |
17 |
18 |
65 |
--------------------------------------------------------------------------------
/src/components/Author.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 | {{ author?.name }}
8 | ({{ author?.institution?.name }})
9 |
10 |
11 |
12 |
39 |
--------------------------------------------------------------------------------
/src/components/Authors.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 | {{ separator }}
12 |
13 |
21 | ...
22 |
23 |
24 |
25 |
26 |
27 |
63 |
--------------------------------------------------------------------------------
/src/components/BetaFeatures.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | You have the following
11 | beta features
14 | enabled.
15 |
16 |
17 |
18 | Prune Level: {{ pruneLevel }}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
49 |
--------------------------------------------------------------------------------
/src/components/BetaSignup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
12 |
13 | Inciteful is changing every day. Stay up to date, get early access to
14 | new features, and have a say in what we develop.
15 |
16 |
17 |
29 |
30 |
31 |
32 |
56 |
--------------------------------------------------------------------------------
/src/components/ConnectorSearch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
70 |
--------------------------------------------------------------------------------
/src/components/DashboardRenderer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ template.title }}
5 |
6 |
7 | {{ template.subtext }}
8 |
9 |
10 |
16 |
17 |
38 |
39 |
40 |
54 |
--------------------------------------------------------------------------------
/src/components/ExternalLinks.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Publisher
10 | |
11 | Full Text from LibKey
17 |
18 |
19 |
20 |
21 |
31 |
--------------------------------------------------------------------------------
/src/components/Faq.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
42 |
--------------------------------------------------------------------------------
/src/components/FavoritePaperButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
51 |
--------------------------------------------------------------------------------
/src/components/FavoritePapers.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 | {{ column.displayName }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{ paper.author[0].name }}
26 | {{ paper.published_year }}
27 | {{ paper.num_cited_by }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
143 |
--------------------------------------------------------------------------------
/src/components/GraphSearch.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | or
9 |
10 | Import BibTeX file
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
117 |
--------------------------------------------------------------------------------
/src/components/LitConnectorPaperSelector.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
{{ searchLabel }}
18 |
25 |
26 |
31 |
32 |
33 |
57 | Connect Paper >>
58 |
59 |
60 |
61 |
62 |
63 |
64 |
72 |
73 |
74 |
75 |
76 |
200 |
--------------------------------------------------------------------------------
/src/components/LitConnectorTour.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
101 |
--------------------------------------------------------------------------------
/src/components/LitReviewBuilder.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
16 |
20 | Add Papers ({{ ids.size }})
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
88 |
--------------------------------------------------------------------------------
/src/components/LitReviewButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
54 |
--------------------------------------------------------------------------------
/src/components/LitReviewHero.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Seed Papers
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | When creating your own graph recommend you have at least five seed
14 | papers to ensure quality results.
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
73 |
--------------------------------------------------------------------------------
/src/components/Loader.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
18 |
19 |
39 |
--------------------------------------------------------------------------------
/src/components/PaperHero.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Constructing and analyzing a graph takes time. If it's not cached
29 | it may take up to a minute for the graph to load. Please be
30 | patient.
31 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
43 | Links:
44 |
45 |
46 |
47 |
48 |
49 |
50 |
90 |
--------------------------------------------------------------------------------
/src/components/PaperHeroStats.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | Cited By
8 | {{ numCitedBy }}
9 |
10 |
11 | Citing
12 | {{ numCiting }}
13 |
14 |
15 | Published
16 | {{ publishedYear }}
17 |
18 |
19 | Open Access
20 | Yes - No
29 |
30 |
31 |
32 |
33 | Papers in Graph
34 |
35 |
40 |
41 |
42 | Citations in Graph
43 |
44 |
49 |
50 |
51 | Graph Depth
52 |
53 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
120 |
--------------------------------------------------------------------------------
/src/components/PaperList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Title
8 |
9 |
10 | First Author
11 |
12 |
13 | Year
14 |
15 |
16 | Cited By
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | {{ paper.published_year }}
35 |
36 |
37 | {{ paper.num_cited_by }}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | {{ paper.doi }}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
55 |
56 |
57 | View All
58 | {{ ids ? ids.length : 0 }}
59 |
60 | View Less
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
192 |
--------------------------------------------------------------------------------
/src/components/PaperPageTour.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
129 |
--------------------------------------------------------------------------------
/src/components/PaperSummary.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 | {{ paper.journal }} {{ paper.published_year && paper.journal ? ',' : '' }}
10 | {{ paper.published_year ? `${paper.published_year}` : '' }}
11 |
12 |
13 |
14 |
15 |
16 |
37 |
--------------------------------------------------------------------------------
/src/components/SaveDropDown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Save
7 |
8 |
9 |
10 |
13 |
15 |
16 |
17 |
21 | Save to Favorites
22 |
23 |
24 |
25 |
26 |
30 | BibTex
31 |
32 |
33 |
34 |
38 | RIS
39 |
40 |
41 |
42 |
46 | Mendeley
47 |
48 |
49 |
50 |
54 | Zotero
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
125 |
--------------------------------------------------------------------------------
/src/components/SearchResults.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Error: {{ errorMsg }}
4 |
5 |
6 |
7 |
11 | We could not find any results. Please check your search and try again.
12 |
13 |
19 |
20 |
42 |
43 |
44 | {{ paper.journal }}
45 |
46 |
49 |
50 |
51 |
52 |
53 | Year:
54 | {{
55 | paper.published_year
56 | }}
57 | Cited By:
58 | {{
59 | paper.num_cited_by
60 | }}
61 | Citing:
62 | {{
63 | paper.num_citing
64 | }}
65 |
66 |
67 |
68 |
69 |
70 |
94 | View Graph >>
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | Search results powered by
104 | OpenAlex .
109 |
110 |
111 |
112 |
178 |
--------------------------------------------------------------------------------
/src/components/SimilarGraph.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
93 |
--------------------------------------------------------------------------------
/src/components/SqlView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
26 |
27 |
28 |
29 |
112 |
--------------------------------------------------------------------------------
/src/components/Stat.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
20 |
21 |
22 |
23 |
29 |
--------------------------------------------------------------------------------
/src/components/StatView.vue:
--------------------------------------------------------------------------------
1 |
2 | - {{ getValue() }}
6 |
7 |
8 |
41 |
--------------------------------------------------------------------------------
/src/components/TableBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
33 |
--------------------------------------------------------------------------------
/src/components/Tour.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
54 |
59 |
--------------------------------------------------------------------------------
/src/components/announcements/AnnouncementBanner.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
38 |
--------------------------------------------------------------------------------
/src/components/announcements/MedSurveyAnnouncement.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We'd like your input! Please complete a 3
5 | minute survey
6 | on how you use Inciteful.
7 |
8 |
9 |
10 |
21 |
--------------------------------------------------------------------------------
/src/components/announcements/ZoteroAnnouncement.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Did you know you can search directly from Zotero? Check out our new Zotero
6 | plugin and see the Twitter
8 | thread on how to use it.
9 |
10 |
11 |
12 |
23 |
--------------------------------------------------------------------------------
/src/components/layout/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 | Paper Discovery
10 |
11 |
12 |
13 |
14 | Literature Connector
15 |
16 |
17 |
18 |
22 | 6° of Einstein
23 |
24 |
25 |
33 |
34 |
38 | About
39 |
40 |
41 |
42 |
46 | Data Sources
47 |
48 |
49 |
67 |
87 |
88 |
89 |
90 | Help support free open source tools for academics. If you found this
91 | site helpful, please cite it:
92 |
93 |
94 | Weishuhn, M. ({{ new Date().getFullYear() }}). Inciteful: Citation
95 | network exploration. Retrieved from https://inciteful.xyz
96 |
97 |
98 |
99 |
100 | © {{ year }} Inciteful. All rights reserved.
101 |
102 |
103 |
104 |
105 |
106 |
107 |
119 |
--------------------------------------------------------------------------------
/src/components/layout/SingleColumn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
25 |
--------------------------------------------------------------------------------
/src/components/modals/AuthorModalContent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Other graph papers by {{ options.author.name }}
4 |
5 |
6 | Back
7 |
8 |
9 |
10 |
11 |
34 |
--------------------------------------------------------------------------------
/src/components/modals/ModalManager.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
18 |
21 |
22 |
23 |
24 |
29 |
68 |
69 |
70 |
71 |
72 |
73 |
132 |
--------------------------------------------------------------------------------
/src/components/modals/NotificationModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
17 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
31 | {{ message1 }}
32 |
33 |
34 | {{ message2 }}
35 |
36 |
37 |
38 |
42 | Close
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
104 |
--------------------------------------------------------------------------------
/src/components/modals/PaperModalButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 | {{ text }}
9 |
10 |
11 |
12 |
57 |
--------------------------------------------------------------------------------
/src/components/modals/PaperModalContent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 | View in Literature Connector >>
11 |
12 |
13 |
14 |
15 |
16 |
17 | Back
18 |
19 |
20 |
21 |
22 |
23 |
24 | Go to Graph
25 |
26 |
27 |
28 |
29 |
30 |
31 | Add to Lit Review
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
167 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 | import router from './router'
4 | import { createPinia } from 'pinia'
5 | import * as Sentry from '@sentry/vue'
6 | import { Integrations } from '@sentry/tracing'
7 | import VueTour from 'v3-tour'
8 | import VueClickAway from 'vue3-click-away'
9 | import Vue3TouchEvents from 'vue3-touch-events'
10 |
11 | import './assets/tailwind.css'
12 | import { useUserStore } from './stores/userStore'
13 | import { emitter } from './utils/emitHelpers'
14 | import { createHead } from "@vueuse/head"
15 |
16 | require('v3-tour/dist/vue-tour.css')
17 |
18 | const app = createApp(App)
19 |
20 | // For options API
21 | app.config.globalProperties.emitter = emitter
22 | // For composition API
23 | app.provide('emitter', emitter)
24 |
25 | if (process.env.NODE_ENV === 'production') {
26 | Sentry.init({
27 | app,
28 | dsn:
29 | 'https://e0f9638a22234f65b69a22a10537ab95@o415910.ingest.sentry.io/5512736',
30 | integrations: [new Integrations.BrowserTracing()],
31 | release: 'inciteful-js@' + process.env.__COMMIT_HASH__,
32 | environment: process.env.NODE_ENV,
33 | tracingOptions: {
34 | trackComponents: true
35 | },
36 | tracesSampleRate: 1.0
37 | })
38 | }
39 |
40 | (async () => {
41 | app.use(createPinia())
42 |
43 | const head = createHead()
44 |
45 | app
46 | .use(router)
47 | .use(VueTour)
48 | .use(VueClickAway)
49 | .use(Vue3TouchEvents)
50 | .use(head)
51 |
52 | app.mount('#app')
53 | })()
54 |
--------------------------------------------------------------------------------
/src/navigation.ts:
--------------------------------------------------------------------------------
1 | function getPaperUrl (id: string) {
2 | return `/p/${id}`
3 | }
4 | function getPaperQueryUrl (id: string) {
5 | return `/p/q/${id}`
6 | }
7 |
8 | export default {
9 | getPaperUrl,
10 | getPaperQueryUrl
11 | }
12 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import qs from 'qs'
2 | import { createRouter, createWebHistory } from 'vue-router'
3 | import Home from '../views/Home.vue'
4 | import pagedata from '../utils/pagedata'
5 |
6 | const routes = [
7 | {
8 | path: '/',
9 | name: 'Home',
10 | component: Home,
11 | meta: {
12 | title: 'Using Citations to Explore Academic Literature',
13 | description:
14 | 'Committed to open access, Inciteful uses the power of graph analysis to help you explore and find the most relevant academic literature.',
15 | canonical: '/'
16 | },
17 | isSecureContext
18 | },
19 | {
20 | path: '/about',
21 | name: 'About',
22 | // route level code-splitting
23 | // this generates a separate chunk (about.[hash].js) for this route
24 | // which is lazy-loaded when the route is visited.
25 | component: () =>
26 | import(/* webpackChunkName: "about" */ '../views/About.vue'),
27 | meta: {
28 | title: 'About',
29 | description: 'About Inciteful'
30 | }
31 | },
32 | {
33 | path: '/einstein',
34 | name: 'Einstein',
35 | component: () =>
36 | import(/* webpackChunkName: "connector" */ '../views/Einstein.vue'),
37 | meta: {
38 | title: 'Six Degrees of Albert Einstein',
39 | description:
40 | "See how your work is connected to one of Einstein's seminal papers."
41 | }
42 | },
43 | {
44 | path: '/data',
45 | name: 'Data',
46 | component: () =>
47 | import(/* webpackChunkName: "data" */ '../views/DataSources.vue'),
48 | meta: {
49 | title: 'Data Sources',
50 | description: 'Where we get our data'
51 | }
52 | },
53 | {
54 | path: '/search',
55 | name: 'Search',
56 | component: () =>
57 | import(/* webpackChunkName: "search" */ '../views/Search.vue'),
58 | meta: {
59 | title: 'Search for Papers'
60 | }
61 | },
62 | {
63 | path: '/beta',
64 | name: 'Beta',
65 | component: () =>
66 | import(/* webpackChunkName: "about" */ '../views/BetaFeatures.vue'),
67 | meta: {
68 | title: 'Enable and Play with Inciteful Beta Features'
69 | }
70 | },
71 | {
72 | path: '/p/q/:pathMatch(.*)',
73 | name: 'PaperDiscoveryQuery',
74 | component: () =>
75 | import(
76 | /* webpackChunkName: "discovery" */ '../views/PaperDiscoveryQuery.vue'
77 | ),
78 | meta: {
79 | title: 'Paper Discovery Query',
80 | description:
81 | "Use our Query tool to make custom queries on the paper's graph."
82 | }
83 | },
84 | {
85 | path: '/p/q',
86 | name: 'LitReviewQuery',
87 | component: () =>
88 | import(/* webpackChunkName: "discovery" */ '../views/LitReviewQuery.vue'),
89 | meta: {
90 | title: 'Literature Review Query',
91 | description:
92 | "Use our Query tool to make custom queries on the graph you've built."
93 | }
94 | },
95 | {
96 | path: '/p',
97 | name: 'LitReview',
98 | component: () =>
99 | import(/* webpackChunkName: "discovery" */ '../views/LitReview.vue'),
100 | meta: {
101 | title: 'Literature Review',
102 | description:
103 | 'Use our Paper Discovery tool to quickly and easily find the most relevant literature.'
104 | }
105 | },
106 | {
107 | path: '/p/:pathMatch(.*)',
108 | name: 'PaperDiscovery',
109 | component: () =>
110 | import(/* webpackChunkName: "discovery" */ '../views/PaperDiscovery.vue'),
111 | meta: {
112 | title: 'Paper Discovery',
113 | description:
114 | 'Use our Paper Discovery tool to quickly and easily find the most relevant literature.'
115 | }
116 | },
117 | {
118 | path: '/c',
119 | name: 'LitConnector',
120 | component: () =>
121 | import(/* webpackChunkName: "connector" */ '../views/LitConnector.vue'),
122 | meta: {
123 | title: 'Literature Connector',
124 | description:
125 | 'Use our Literature Connector to Discover How Two Papers are Connected'
126 | }
127 | },
128 | {
129 | path: '/e',
130 | name: 'ToolExport',
131 | component: () =>
132 | import(/* webpackChunkName: "connector" */ '../views/Export.vue'),
133 | meta: {
134 | title: 'Export',
135 | description: 'Export your search to Mendeley or Zotero'
136 | }
137 | },
138 | {
139 | path: '/:pathMatch(.*)*',
140 | name: 'not-found',
141 | component: () =>
142 | import(/* webpackChunkName: "about" */ '../views/NotFound.vue'),
143 | meta: {
144 | title: 'Page Not Found'
145 | }
146 | },
147 | // if you omit the last `*`, the `/` character in params will be encoded when resolving or pushing
148 | {
149 | path: '/:pathMatch(.*)',
150 | name: 'bad-not-found',
151 | component: () =>
152 | import(/* webpackChunkName: "about" */ '../views/NotFound.vue'),
153 | meta: {
154 | title: 'Page Not Found'
155 | }
156 | }
157 | ]
158 |
159 | const router = createRouter({
160 | history: createWebHistory(process.env.BASE_URL),
161 | routes,
162 |
163 | scrollBehavior(_to, _from, savedPosition) {
164 | if (savedPosition) {
165 | return savedPosition
166 | } else {
167 | return { left: 0, top: 0 }
168 | }
169 | },
170 | // @ts-ignore
171 | parseQuery: qs.parse,
172 | stringifyQuery: function (params) {
173 | const result = qs.stringify(params, {
174 | arrayFormat: 'brackets'
175 | })
176 | return result || ''
177 | }
178 | })
179 |
180 | router.afterEach(to => {
181 | pagedata.setTitle(to.meta.title as string)
182 | pagedata.setDescription(to.meta.description as string)
183 | })
184 |
185 | export default router
186 |
--------------------------------------------------------------------------------
/src/shim-declarations.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'bib2json' {
2 | import bib2json from 'bib2json'
3 | export default bib2json
4 | }
5 | declare module 'zotero-api-client' {
6 | import api from 'zotero-api-client'
7 | export default api
8 | }
9 |
--------------------------------------------------------------------------------
/src/shim-tsx.d.ts:
--------------------------------------------------------------------------------
1 | import Vue, { VNode } from 'vue'
2 |
3 | declare global {
4 | namespace JSX {
5 | // tslint:disable no-empty-interface
6 | type Element = VNode
7 | // tslint:disable no-empty-interface
8 | type ElementClass = Vue
9 | interface IntrinsicElements {
10 | [elem: string]: any;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/shim-vue.d.ts:
--------------------------------------------------------------------------------
1 | import mitt from 'mitt'
2 | declare module '*.vue' {
3 | import Vue from 'vue'
4 | export default Vue
5 | }
6 | declare module '@vue/runtime-core' {
7 | interface ComponentCustomProperties {
8 | emitter: mitt;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/stores/userStore.ts:
--------------------------------------------------------------------------------
1 |
2 | import { defineStore } from 'pinia'
3 | import { UserData } from '../types/userTypes';
4 | import { PaperID } from '../types/incitefulTypes';
5 |
6 | export const useUserStore = defineStore({
7 | id: 'loggedInUser',
8 | state: () => {
9 | return {
10 | userDataDoc: undefined as UserData | undefined,
11 | enabled: process.env.VUE_APP_SHOW_LOGIN == "true"
12 | }
13 | },
14 | actions: {
15 | async saveUser(user: UserData) {
16 | throw new Error("Not implemented")
17 | },
18 | async awaitUserDataLoad(f: () => void) {
19 | if (this.isSignedIn) {
20 | if (this.userDataDoc) {
21 | f()
22 | } else {
23 | setTimeout(() => this.awaitUserDataLoad(f), 100)
24 | }
25 | }
26 | },
27 | async addFavorite(id: PaperID) {
28 | await this.addFavorites([id])
29 | },
30 | async addFavorites(ids: PaperID[]) {
31 | this.awaitUserDataLoad(async () => {
32 | if (this.userDataDoc) {
33 | throw new Error("Not implemented")
34 | }
35 | })
36 | },
37 | async removeFavorite(id: PaperID) {
38 | this.awaitUserDataLoad(async () => {
39 | if (this.userDataDoc) {
40 | throw new Error("Not implemented")
41 | }
42 | })
43 | },
44 | async toggleFavorite(id: PaperID) {
45 | if (!this.isPaperFavorite(id))
46 | this.addFavorite(id)
47 | else
48 | this.removeFavorite(id)
49 | }
50 | },
51 | getters: {
52 | userData(state) {
53 | return state.userDataDoc
54 | },
55 | isSignedIn(state): boolean {
56 | return false
57 | },
58 | userName(state): string | undefined {
59 | throw new Error("Not implemented")
60 | },
61 | userId(state): string | undefined {
62 | throw new Error("Not implemented")
63 | },
64 | initial(): string | undefined {
65 | return (this.userName && this.userName.length > 0 ? this.userName[0].toUpperCase() : undefined)
66 | },
67 | isPaperFavorite() {
68 | return (id: PaperID) => {
69 | if (this.userData)
70 | return this.userData.favoritePapers.some(x => x == id)
71 | else return false
72 | }
73 | },
74 |
75 | },
76 | })
--------------------------------------------------------------------------------
/src/types/graphTypes.ts:
--------------------------------------------------------------------------------
1 | import { LayoutOptions, Core } from 'cytoscape';
2 | import { Paper, Connection, Path, PaperID } from './incitefulTypes';
3 | import { IModalOptions } from './modalTypes';
4 |
5 |
6 | export interface GraphModalOptions extends IModalOptions {
7 | connectTo?: PaperID;
8 | }
9 |
10 | export interface GraphData {
11 | type: string;
12 | papers?: Paper[];
13 | connections?: Connection[];
14 | paths?: Path[];
15 | toId?: PaperID;
16 | fromId?: PaperID;
17 | sourcePaperIds?: PaperID[];
18 | modalOptions?: GraphModalOptions;
19 | }
20 |
21 | export class IncitefulGraph {
22 | cy: Core;
23 | sourcePaperIds?: PaperID[];
24 | constructor(cy: Core, sourcePaperIds?: PaperID[]) {
25 | this.cy = cy;
26 | this.sourcePaperIds = sourcePaperIds;
27 | this.cy.on('resize', () => {
28 | this.centerSource();
29 | });
30 |
31 | this.cy.ready(() => {
32 | this.centerSource();
33 | });
34 | }
35 |
36 | centerSource() {
37 | const sourcePaperId = this.sourcePaperIds && this.sourcePaperIds.length == 1 ? this.sourcePaperIds[0] : undefined;
38 | if (this.cy && (this.cy.height() > 600 || !sourcePaperId)) {
39 | this.cy.fit();
40 | } else {
41 | const j = this.cy.$(`[id = "${sourcePaperId}"]`);
42 | this.cy.reset().center(j);
43 | }
44 | }
45 |
46 | filterNodes(ids: Set) {
47 | this.cy.nodes().forEach(node => {
48 | if (ids.has(node.id())) {
49 | node.removeClass('disabled');
50 | } else {
51 | node.addClass('disabled');
52 | }
53 | });
54 | }
55 |
56 | highlightNodes(ids: Set) {
57 | this.cy.nodes().forEach(node => {
58 | if (ids.has(node.id())) {
59 | node.addClass('highlighted');
60 | } else {
61 | node.removeClass('highlighted');
62 | }
63 | });
64 | }
65 |
66 | resize() {
67 | this.cy.resize();
68 | }
69 |
70 | zoom(level?: number) {
71 | return this.cy.zoom(level);
72 | }
73 |
74 | curZoom(): number {
75 | return this.cy.zoom();
76 | }
77 |
78 | renderLayout(layoutParams: LayoutOptions) {
79 | const layout = this.cy.layout(layoutParams);
80 | layout.run();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/types/incitefulTypes.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable camelcase */
2 |
3 | import { Emitter, EventType } from 'mitt'
4 |
5 | export type IncitefulEmitter = Emitter>
6 |
7 | export interface IIndexable {
8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
9 | [key: string]: any
10 | }
11 |
12 | export type PaperID = string
13 |
14 | export enum ReferenceManagers {
15 | Zotero = 'Zotero',
16 | Mendeley = 'Mendeley',
17 | EndNote = 'EndNote',
18 | RefWorks = 'RefWorks'
19 | }
20 |
21 | export interface PaperAutosuggest {
22 | id: PaperID
23 | title: string
24 | authors: string
25 | num_cited_by: number
26 | }
27 |
28 | function authorListDisplay(a: Author[] | undefined): string {
29 | if (!a) return ''
30 |
31 | let display = a
32 | .slice(0, 3)
33 | .map(x => x.name)
34 | .join(', ')
35 |
36 | if (a.length > 3) display += ', et al'
37 |
38 | return display
39 | }
40 |
41 | export function paperIntoPaperAutosuggest(p: Paper): PaperAutosuggest {
42 | return {
43 | id: p.id,
44 | title: p.title || 'NA',
45 | authors: authorListDisplay(p.author),
46 | num_cited_by: p.num_cited_by
47 | }
48 | }
49 |
50 | export interface Paper {
51 | id: PaperID
52 | doi?: string
53 | author: Author[]
54 | title: string
55 | published_year: number
56 | journal?: string
57 | pages: string
58 | volume: string
59 | num_citing: number
60 | num_cited_by: number
61 | distance?: number
62 | path_count?: number
63 | citing?: PaperID[]
64 | cited_by?: PaperID[]
65 | }
66 |
67 | export interface LockedPaper extends Paper {
68 | isLocked: boolean
69 | }
70 |
71 | export interface Author {
72 | author_id?: number
73 | name: string
74 | sequence: number
75 | position?: string
76 | institution?: Institution
77 | }
78 |
79 | export interface Institution {
80 | id?: number
81 | name: string
82 | }
83 |
84 | export interface PaperConnector {
85 | source: PaperID
86 | papers: Paper[]
87 | paths: Path[]
88 | connections: Connection[]
89 | num_paths: number
90 | papers_searched: number
91 | max_hops: number
92 | }
93 | export interface ExternalIds {
94 | DOI?: string | null
95 | PMID?: string | null
96 | PMCID?: string | null
97 | }
98 | export interface Connection {
99 | citing: string
100 | cited: string
101 | }
102 |
103 | export type Path = PaperID[]
104 |
105 | export type QueryValue = string | number | Author[]
106 |
107 | export type QueryRow = Record
108 |
109 | export type QueryResults = QueryRow[]
110 |
111 | export interface Faq {
112 | question: string
113 | answer: string
114 | }
115 |
--------------------------------------------------------------------------------
/src/types/modalTypes.ts:
--------------------------------------------------------------------------------
1 | import { PaperID, Author } from './incitefulTypes';
2 |
3 | export interface IModalOptions {
4 | previousScreen?: ModalOptions;
5 | }
6 |
7 | export interface PaperModalOptions extends IModalOptions {
8 | paperId: PaperID;
9 | connectTo?: PaperID;
10 | }
11 |
12 | export interface AuthorModalOptions extends IModalOptions {
13 | author: Author;
14 | contextIds?: PaperID[];
15 | }
16 |
17 | export interface SaveCollectionAction {
18 | contextIds: PaperID[];
19 | }
20 |
21 | export type CollectionModalActions = SaveCollectionAction;
22 |
23 | export function isSaveCollectionAction(
24 | options: CollectionModalActions
25 | ): options is SaveCollectionAction {
26 | return (options as SaveCollectionAction).contextIds !== undefined;
27 | }
28 |
29 |
30 | export type ModalOptions = PaperModalOptions | AuthorModalOptions;
31 |
32 | export function isPaperModalOptons(
33 | options: ModalOptions
34 | ): options is PaperModalOptions {
35 | return (options as PaperModalOptions).paperId !== undefined;
36 | }
37 |
38 | export function isAuthorModalOptions(
39 | options: ModalOptions
40 | ): options is AuthorModalOptions {
41 | return (options as AuthorModalOptions).author !== undefined;
42 | }
43 |
44 | export interface NotificationModalOptions {
45 | message1: string,
46 | message2: string
47 | }
--------------------------------------------------------------------------------
/src/types/openAlexTypes.ts:
--------------------------------------------------------------------------------
1 |
2 | export interface OAAutosuggestResponse {
3 | meta: Meta
4 | results: OAAutosuggestResult[]
5 | }
6 |
7 | export interface Meta {
8 | count: number
9 | db_response_time_ms: number
10 | page: number
11 | per_page: number
12 | }
13 |
14 |
15 | export interface OAAutosuggestResult {
16 | id: string;
17 | display_name: string;
18 | hint: string;
19 | cited_by_count: number;
20 | works_count: number;
21 | entityType: string;
22 | externalId: string;
23 | }
24 |
25 |
26 | export interface OAPaperSearchResults {
27 | meta: Meta
28 | results: OAPaper[]
29 | }
30 |
31 |
32 | export interface OAPaper {
33 | id: string
34 | doi?: string
35 | title?: string
36 | display_name?: string
37 | publication_year?: number
38 | publication_date?: string
39 | ids: OAWorkIds
40 | primary_location: OAPrimaryLocation
41 | type?: string
42 | open_access: OAOpenAccess
43 | authorships: OAAuthorship[]
44 | cited_by_count: number
45 | biblio: OABiblio
46 | is_retracted: boolean
47 | is_paratext?: boolean
48 | concepts?: OAConcept[]
49 | mesh?: OAMesh[]
50 | referenced_works: string[]
51 | related_works: string[]
52 | cited_by_api_url: string
53 | updated_date: string
54 | created_date: string
55 | }
56 |
57 | export interface OAWorkIds {
58 | openalex?: string
59 | doi?: string
60 | mag?: string
61 | pmid?: string
62 | pmcid?: string
63 | }
64 |
65 | export interface OAPrimaryLocation {
66 | is_oa: boolean
67 | landing_page_url: string
68 | pdf_url: string
69 | source: OALocationSource
70 | license: string
71 | version: string
72 | is_accepted: boolean
73 | is_published: boolean
74 | }
75 |
76 | export interface OALocationSource {
77 | id: string
78 | display_name: string
79 | issn_l: string
80 | issn: string[]
81 | is_oa: boolean
82 | is_in_doaj: boolean
83 | host_organization: string
84 | host_organization_name: string
85 | host_organization_lineage: string[]
86 | host_organization_lineage_names: string[]
87 | type: string
88 | }
89 |
90 | export interface OAOpenAccess {
91 | is_oa: boolean
92 | oa_status: string
93 | oa_url: string
94 | }
95 |
96 | export interface OAMesh {
97 | descriptor_ui: string
98 | descriptor_name: string
99 | qualifier_ui: string
100 | qualifier_name?: string
101 | is_major_topic: boolean
102 | }
103 |
104 | export interface OAAuthorship {
105 | author_position: string
106 | author: OAAuthor
107 | institutions: OAInstitution[]
108 | raw_affiliation_string: string
109 | }
110 |
111 | export interface OAAuthor {
112 | id: string
113 | display_name: string
114 | orcid?: string
115 | }
116 |
117 | export interface OAInstitution {
118 | id?: string
119 | display_name: string
120 | ror: string
121 | country_code: string
122 | type: string
123 | }
124 |
125 | export interface OABiblio {
126 | volume: string
127 | issue: string
128 | first_page: string
129 | last_page: string
130 | }
131 |
132 | export interface OAConcept {
133 | id: string
134 | wikidata: string
135 | display_name: string
136 | level: number
137 | score: string
138 | }
139 |
140 |
--------------------------------------------------------------------------------
/src/types/userTypes.ts:
--------------------------------------------------------------------------------
1 | import { PaperID } from "./incitefulTypes";
2 |
3 | export interface UserData {
4 | id: string,
5 | favoritePapers: PaperID[]
6 | }
7 |
8 | export enum ItemVisibility {
9 | Hidden = "HIDDEN",
10 | Public = "PUBLIC",
11 | }
12 |
13 | export interface IncitefulCollection {
14 | id: string | null,
15 | ownerId: string,
16 | parentID: string | null,
17 | visibility: ItemVisibility,
18 | name: string,
19 | papers: IncitefulCollectionItem[]
20 | dateCreated: Date
21 | }
22 |
23 | export enum IncitefulCollectionItemSource {
24 | Inciteful = "INCITEFUL",
25 | Zotero = "ZOTERO",
26 | }
27 | export interface IncitefulCollectionItem {
28 | paperId: PaperID,
29 | dateAdded: Date,
30 | source: IncitefulCollectionItemSource,
31 | }
32 |
--------------------------------------------------------------------------------
/src/types/yaml.ts:
--------------------------------------------------------------------------------
1 | declare module '*.yaml' {
2 | const data: any
3 | export default data
4 | }
--------------------------------------------------------------------------------
/src/utils/bib.ts:
--------------------------------------------------------------------------------
1 | import bib2json from 'bib2json'
2 |
3 | function idsFromBib(bibText: string) {
4 | const results = bib2json(bibText)
5 | const ids: string[] = []
6 | results.entries.forEach(
7 | (element: {
8 | Fields: { incitefulid: string; magid: string; doi: string; url: string; URL: string; DOI: string };
9 | }) => {
10 | if (element.Fields.incitefulid) {
11 | ids.push(element.Fields.incitefulid)
12 | } else if (element.Fields.magid) {
13 | ids.push(`MAG:${element.Fields.magid}`)
14 | } else if (element.Fields.doi) {
15 | ids.push(element.Fields.doi)
16 | } else if (element.Fields.DOI) {
17 | ids.push(element.Fields.DOI)
18 | }
19 |
20 | if (element.Fields.url) {
21 | ids.push(element.Fields.url)
22 | }
23 | if (element.Fields.URL) {
24 | ids.push(element.Fields.URL)
25 | }
26 | }
27 | )
28 |
29 | return ids
30 | }
31 |
32 | export default {
33 | idsFromBib
34 | }
35 |
--------------------------------------------------------------------------------
/src/utils/doi.ts:
--------------------------------------------------------------------------------
1 | import doiRegex from 'doi-regex'
2 |
3 | function isExactDoi(doi: string) {
4 | if (doi.startsWith('http://doi.org/')) {
5 | doi = doi.slice('http://doi.org/'.length)
6 | }
7 |
8 | return !doi ? false : doiRegex({ exact: true }).test(doi)
9 | }
10 |
11 |
12 | function buildDoi(doi: string): string | undefined {
13 | if (!doi) return undefined
14 |
15 | if (doi.startsWith('http://doi.org/')) {
16 | return doi
17 | } else {
18 | if (!isExactDoi(doi))
19 | return undefined
20 |
21 | if (doi.startsWith('doi:')) {
22 | doi = doi.slice('doi:'.length)
23 | }
24 |
25 | return `https://doi.org/${doi}`
26 | }
27 | }
28 |
29 | export default {
30 | isExactDoi,
31 | buildDoi
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/emitHelpers.ts:
--------------------------------------------------------------------------------
1 | import mitt from 'mitt'
2 | import { IncitefulEmitter, PaperID } from '../types/incitefulTypes';
3 | import { ModalOptions, NotificationModalOptions } from '../types/modalTypes';
4 |
5 | export const EmitEvents = {
6 | ShowModal: Symbol("show_modal"),
7 | AddToLitReview: Symbol("add_to_lit_review"),
8 | ShowNotification: Symbol("show_notification"),
9 | GoToPaper: Symbol("go_to_paper"),
10 | GraphLoaded: Symbol("graph_loaded"),
11 | }
12 |
13 | export const emitter = mitt() as IncitefulEmitter
14 |
15 | export const showModalHelper = (modalOptions: ModalOptions) => {
16 | emitter.emit(EmitEvents.ShowModal, modalOptions)
17 | }
18 |
19 | export const showNotificationHelper = (options: NotificationModalOptions) => {
20 | emitter.emit(EmitEvents.ShowNotification, options)
21 | }
22 |
23 | export const graphLoadedHelper = () => {
24 | emitter.emit(EmitEvents.GraphLoaded)
25 | }
26 | export const addToLitReviewHelper = (id: PaperID) => {
27 | emitter.emit(EmitEvents.AddToLitReview, id)
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/exportUtils.ts:
--------------------------------------------------------------------------------
1 | import { Paper } from '../types/incitefulTypes';
2 | import { useHead } from '@vueuse/head';
3 |
4 | export function sendDataUpdated() {
5 | document.dispatchEvent(
6 | new Event('ZoteroItemUpdated', {
7 | bubbles: true,
8 | cancelable: true
9 | })
10 | )
11 | }
12 |
13 | export function setExportHeaders(papers: Paper[]) {
14 | if (!papers) return
15 |
16 | const meta: { name: string; content: string | undefined }[] = [];
17 | papers.forEach((paper: Paper) => {
18 | if (paper.doi) {
19 | meta.push({
20 | name: 'citation_doi',
21 | content: paper.doi
22 | })
23 | }
24 | })
25 |
26 | useHead({ meta })
27 |
28 | setTimeout(() => {
29 | sendDataUpdated()
30 | }, 500)
31 | }
32 |
--------------------------------------------------------------------------------
/src/utils/graphing/graph.ts:
--------------------------------------------------------------------------------
1 | import cytoscape, { Core, NodeSingular, ElementDefinition } from 'cytoscape'
2 | // @ts-ignore
3 | import contextMenus from 'cytoscape-context-menus'
4 | import 'cytoscape-context-menus/cytoscape-context-menus.css'
5 | // @ts-ignore
6 | import fcose from 'cytoscape-fcose'
7 | // @ts-ignore
8 | import klay from 'cytoscape-klay'
9 | import graphStyles from './graphStyles'
10 | import similar from './similar'
11 | import connector from './connector'
12 | import popper from 'cytoscape-popper'
13 | import tippy from 'tippy.js'
14 | import 'tippy.js/dist/tippy.css'
15 | import {
16 | PaperID
17 | } from '@/types/incitefulTypes'
18 | import {
19 | GraphData,
20 | IncitefulGraph,
21 | GraphModalOptions
22 | } from "@/types/graphTypes"
23 | import { Emitter } from 'mitt'
24 | import { PaperModalOptions } from "../../types/modalTypes"
25 | import { showModalHelper } from '../emitHelpers';
26 | import { IIndexable } from '../../types/incitefulTypes';
27 |
28 |
29 | cytoscape.use(popper)
30 | cytoscape.use(fcose)
31 | cytoscape.use(klay)
32 | cytoscape.use(contextMenus)
33 |
34 | function hideTippy(node: NodeSingular) {
35 | const tippy = node.data('tippy')
36 |
37 | if (tippy != null) {
38 | tippy.hide()
39 | }
40 | }
41 |
42 | function createTippys(cy: Core) {
43 | cy.nodes().forEach(node => {
44 | const content = node.data('tippyContent')
45 |
46 | if (content) {
47 | const ref = node.popperRef() // used only for positioning
48 |
49 | // A dummy element must be passed as tippy only accepts dom element(s) as the target
50 | // https://atomiks.github.io/tippyjs/v6/constructor/#target-types
51 | const dummyDomEle = document.createElement('div')
52 |
53 | const tip = tippy(dummyDomEle, {
54 | // tippy props:
55 | getReferenceClientRect: ref.getBoundingClientRect, // https://atomiks.github.io/tippyjs/v6/all-props/#getreferenceclientrect
56 | trigger: 'manual', // mandatory, we cause the tippy to show programmatically.
57 | arrow: true,
58 | allowHTML: true,
59 | hideOnClick: false,
60 | interactive: false,
61 | appendTo: document.body,
62 | // your own custom props
63 | // content prop can be used when the target is a single element https://atomiks.github.io/tippyjs/v6/constructor/#prop
64 | content
65 | })
66 |
67 | node.data('tippy', tip)
68 | const els: PaperID[] = node.data('elsToHighlight')
69 |
70 | node.on('mouseover', function () {
71 | // If the context menu is open, don't trigger the mouseover actions.
72 | if (
73 | cy.scratch &&
74 | cy.scratch().cycontextmenus &&
75 | cy.scratch().cycontextmenus.cxtMenu &&
76 | cy.scratch().cycontextmenus.cxtMenu.style.display !== 'none'
77 | ) {
78 | return
79 | }
80 |
81 | tip.show()
82 | setTimeout(() => tip.hide(), 5000)
83 |
84 | if (els) {
85 | els.forEach((id: PaperID) => {
86 | const el = cy.$(`[id = "${id}"]`)
87 | if (el) el.addClass('highlighted')
88 | })
89 | }
90 |
91 | node.addClass('highlighted')
92 | cy.nodes()
93 | .not(node)
94 | .forEach(hideTippy)
95 | })
96 |
97 | node.on('mouseout', function () {
98 | if (els) {
99 | els.forEach(id => {
100 | const el = cy.$(`[id = "${id}"]`)
101 | if (el) el.removeClass('highlighted')
102 | })
103 | }
104 |
105 | node.removeClass('highlighted')
106 | tip.hide()
107 | })
108 | }
109 | })
110 | }
111 | function setupTippy(cy: Core, modalOptions: GraphModalOptions) {
112 | createTippys(cy)
113 |
114 | const hideAllTippies = function () {
115 | cy.nodes().forEach(hideTippy)
116 | }
117 |
118 | cy.on('tap', 'node', function (ev) {
119 | hideAllTippies()
120 | const id = ev.target.data('id')
121 | if (id) {
122 | (modalOptions as PaperModalOptions).paperId = id
123 | showModalHelper(modalOptions as PaperModalOptions)
124 | }
125 | })
126 |
127 | cy.on('tap', 'edge', function () {
128 | hideAllTippies()
129 | })
130 |
131 | cy.on('zoom pan', function () {
132 | hideAllTippies()
133 | })
134 | }
135 |
136 | function loadBaseGraph(
137 | elements: ElementDefinition[],
138 | container: HTMLElement,
139 | modalOptions: GraphModalOptions
140 | ) {
141 | const cy = cytoscape({
142 | container,
143 | autounselectify: true,
144 | userZoomingEnabled: true,
145 | userPanningEnabled: true,
146 | elements,
147 | style: graphStyles.default,
148 | layout: { name: 'random' }
149 | })
150 |
151 | setupTippy(cy, modalOptions)
152 |
153 | return cy
154 | }
155 |
156 | function loadGraph(
157 | graphData: GraphData,
158 | container: HTMLElement,
159 | bus: Emitter>,
160 | minDate: number,
161 | maxDate: number
162 | ) {
163 | let elements
164 | let layoutParams
165 | let contextMenuOptions
166 |
167 | if (graphData.type === 'similar') {
168 | elements = similar.buildElements(graphData, minDate, maxDate)
169 | layoutParams = similar.buildLayout()
170 | }
171 |
172 | if (graphData.type === 'connector') {
173 | elements = connector.buildElements(graphData, minDate, maxDate)
174 | layoutParams = connector.buildLayout()
175 | contextMenuOptions = connector.contextMenu(bus)
176 | }
177 |
178 | if (elements === undefined) {
179 | console.error('Graph type not supported: ' + graphData.type)
180 | return new IncitefulGraph(cytoscape())
181 | }
182 |
183 | const cy = loadBaseGraph(
184 | elements,
185 | container,
186 | graphData.modalOptions ?? {}
187 | )
188 |
189 | const graph = new IncitefulGraph(cy, graphData.sourcePaperIds)
190 |
191 | // Context menus are not working on Safari
192 | // https://github.com/iVis-at-Bilkent/cytoscape.js-context-menus/issues/55
193 | const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
194 | if (contextMenuOptions && !isSafari) {
195 | const cyany = cy as IIndexable
196 |
197 | if (cyany && cyany.contextMenus)
198 | cyany.contextMenus(contextMenuOptions)
199 | }
200 |
201 | // @ts-ignore
202 | graph.renderLayout(layoutParams)
203 |
204 | return graph
205 | }
206 |
207 | export default {
208 | loadGraph
209 | }
210 |
--------------------------------------------------------------------------------
/src/utils/graphing/graphStyles.ts:
--------------------------------------------------------------------------------
1 | import { EdgeSingular, Stylesheet } from 'cytoscape'
2 |
3 | const style: Stylesheet[] = [
4 | {
5 | selector: 'node',
6 | style: {
7 | label: 'data(title)',
8 | width: 'data(size)',
9 | height: 'data(size)',
10 | color: 'white',
11 | 'background-color': 'data(color)',
12 | 'border-width': 2,
13 | 'border-color': 'white',
14 | 'text-outline-width': 6,
15 | 'text-outline-color': 'data(color)',
16 | 'font-size': '18px',
17 | 'text-valign': 'center',
18 | 'text-halign': 'center',
19 | 'overlay-padding': '6px'
20 | }
21 | },
22 | {
23 | selector: 'node[group=\'isSource\']',
24 | style: {
25 | 'z-index': 1,
26 | 'border-width': '9px',
27 | 'border-color': '#AAD8FF',
28 | 'border-opacity': 0.7
29 | }
30 | },
31 | {
32 | selector: 'node.disabled',
33 | style: {
34 | 'background-color': '#9fa6b2',
35 | 'text-outline-color': '#9fa6b2'
36 | }
37 | },
38 | {
39 | selector: 'edge',
40 | style: {
41 | 'curve-style': 'straight',
42 | color: 'data(color)',
43 | 'overlay-padding': '2px',
44 | opacity: (edge: EdgeSingular) => { return Number(edge.data('opacity')) },
45 | width: 2
46 | }
47 | },
48 | {
49 | selector: '.highlighted',
50 | style: {
51 | 'z-index': 999999
52 | }
53 | },
54 | {
55 | selector: 'node.highlighted',
56 | style: {
57 | 'border-width': '12px',
58 | 'border-color': '#AAD8FF',
59 | 'border-opacity': 0.7
60 | }
61 | },
62 | {
63 | selector: 'edge.highlighted',
64 | style: {
65 | width: '4px',
66 | 'line-color': '#AAD8FF',
67 | opacity: 1
68 | }
69 | },
70 | {
71 | selector: 'node:selected',
72 | style: {
73 | 'border-width': '6px',
74 | 'border-color': '#AAD8FF',
75 | 'border-opacity': 0.5,
76 | 'background-color': '#77828C',
77 | 'text-outline-color': '#77828C'
78 | }
79 | }
80 | ]
81 |
82 | export default {
83 | default: style
84 | }
85 |
--------------------------------------------------------------------------------
/src/utils/keywords.ts:
--------------------------------------------------------------------------------
1 | import { Paper } from '@/types/incitefulTypes'
2 | import { stemmer } from 'stemmer'
3 |
4 | const stop = new Set([
5 | 'new',
6 | 'i',
7 | 'me',
8 | 'my',
9 | 'myself',
10 | 'we',
11 | 'our',
12 | 'ours',
13 | 'ourselves',
14 | 'you',
15 | 'your',
16 | 'yours',
17 | 'yourself',
18 | 'yourselves',
19 | 'he',
20 | 'him',
21 | 'his',
22 | 'himself',
23 | 'she',
24 | 'her',
25 | 'hers',
26 | 'herself',
27 | 'it',
28 | 'its',
29 | 'itself',
30 | 'they',
31 | 'them',
32 | 'their',
33 | 'theirs',
34 | 'themselves',
35 | 'what',
36 | 'which',
37 | 'who',
38 | 'whom',
39 | 'this',
40 | 'that',
41 | 'these',
42 | 'those',
43 | 'am',
44 | 'is',
45 | 'are',
46 | 'was',
47 | 'were',
48 | 'be',
49 | 'been',
50 | 'being',
51 | 'have',
52 | 'has',
53 | 'had',
54 | 'having',
55 | 'do',
56 | 'does',
57 | 'did',
58 | 'doing',
59 | 'a',
60 | 'an',
61 | 'the',
62 | 'and',
63 | 'but',
64 | 'if',
65 | 'or',
66 | 'because',
67 | 'as',
68 | 'until',
69 | 'while',
70 | 'of',
71 | 'at',
72 | 'by',
73 | 'for',
74 | 'with',
75 | 'about',
76 | 'based',
77 | 'against',
78 | 'between',
79 | 'into',
80 | 'through',
81 | 'during',
82 | 'before',
83 | 'after',
84 | 'above',
85 | 'below',
86 | 'to',
87 | 'from',
88 | 'up',
89 | 'down',
90 | 'in',
91 | 'out',
92 | 'on',
93 | 'off',
94 | 'over',
95 | 'under',
96 | 'again',
97 | 'further',
98 | 'then',
99 | 'once',
100 | 'here',
101 | 'there',
102 | 'when',
103 | 'where',
104 | 'why',
105 | 'how',
106 | 'all',
107 | 'any',
108 | 'both',
109 | 'each',
110 | 'few',
111 | 'more',
112 | 'most',
113 | 'other',
114 | 'some',
115 | 'such',
116 | 'no',
117 | 'nor',
118 | 'not',
119 | 'only',
120 | 'own',
121 | 'same',
122 | 'so',
123 | 'than',
124 | 'too',
125 | 'very',
126 | 's',
127 | 't',
128 | 'can',
129 | 'will',
130 | 'just',
131 | 'don',
132 | 'should',
133 | 'now'
134 | ])
135 |
136 | export interface TermCount {
137 | term: string;
138 | count: number;
139 | }
140 |
141 | function extract(papers: Paper[]): TermCount[] {
142 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
143 | const keywords: Record = {}
144 | papers.forEach(p => {
145 | const title = p.title.replace(/\W/g, ' ').toLowerCase()
146 | new Set(title.split(' ')).forEach(k => {
147 | if (k && !stop.has(k) && k.length > 1) {
148 | keywords[k] ? keywords[k]++ : (keywords[k] = 1)
149 | }
150 | })
151 | })
152 |
153 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
154 | const stemmedKeywords: Record = {}
155 | Object.entries(keywords).forEach(k => {
156 | const sk = stemmer(k[0])
157 | if (!stemmedKeywords[sk]) {
158 | const tc: TermCount = { term: k[0], count: k[1] }
159 | stemmedKeywords[sk] = tc
160 | } else {
161 | stemmedKeywords[sk].count = stemmedKeywords[sk].count + k[1]
162 | }
163 | })
164 |
165 | const groupedKeywords = Object.entries(stemmedKeywords)
166 | .map(k => k[1])
167 | .filter(k => k.count > 1)
168 |
169 | groupedKeywords.sort((a, b) => b.count - a.count)
170 | return groupedKeywords.slice(0, 15)
171 | }
172 |
173 | export default {
174 | extract
175 | }
176 |
--------------------------------------------------------------------------------
/src/utils/logging.ts:
--------------------------------------------------------------------------------
1 | import * as Sentry from '@sentry/browser'
2 | import { AxiosError } from 'axios'
3 |
4 | export function logError(err: AxiosError) {
5 | Sentry.setExtra('error', err)
6 | Sentry.captureException(err)
7 | }
8 |
9 | export function logInfo(message: string, obj: unknown) {
10 | Sentry.withScope(scope => {
11 | Sentry.setExtra('obj', obj)
12 | Sentry.captureMessage(message, scope)
13 | })
14 | }
15 |
--------------------------------------------------------------------------------
/src/utils/openalexApi.ts:
--------------------------------------------------------------------------------
1 | import {
2 | OAAuthorship,
3 | OAAutosuggestResult,
4 | OAAutosuggestResponse,
5 | OAPaper,
6 | OAPaperSearchResults
7 | } from '../types/openAlexTypes'
8 | import { Author, PaperAutosuggest, Paper } from '../types/incitefulTypes'
9 | import axios, { AxiosError, AxiosResponse } from 'axios'
10 | import { logError } from './logging'
11 | import doiHelpers from './doi'
12 |
13 | function handleServiceErr(err: AxiosError) {
14 | if (err && err.response && err.response.status !== 404) {
15 | logError(err)
16 | }
17 | }
18 |
19 | export function searchOpenAlex(query: string): Promise {
20 | if (query == null || query == undefined || query == "")
21 | return Promise.resolve([])
22 |
23 | const doi = doiHelpers.buildDoi(query)
24 | if (doi) {
25 | return getOAPaper(doi).then(p => {
26 | if (p)
27 | return [convertOAPaperToPaper(p)]
28 | else
29 | return Promise.resolve([])
30 | })
31 | } else {
32 | //remove non alphanumeric characters
33 | query = query.replace(/[^a-zA-Z0-9 ]/g, ' ')
34 | return titleSearch(query).then(papers => {
35 | if (papers.length > 0)
36 | return papers
37 |
38 | return fullSearch(query).then(papers => {
39 | if (papers.length > 0)
40 | return papers
41 |
42 | return []
43 | })
44 | })
45 | }
46 | }
47 |
48 | function titleSearch(query: string): Promise {
49 | return genericSearch(query, 'https://api.openalex.org/works?filter=title.search:')
50 | }
51 |
52 | function fullSearch(query: string): Promise {
53 | return genericSearch(query, 'https://api.openalex.org/works?search=')
54 | }
55 |
56 | function genericSearch(query: string, searchUrl: string): Promise {
57 | return axios
58 | .get(
59 | `${searchUrl}${encodeURIComponent(
60 | query
61 | )}&mailto=info@inciteful.xyz`
62 | )
63 | .then((res: AxiosResponse) => {
64 | if (res.data && res.data.results) {
65 | const results = res.data.results
66 | .map(convertOAPaperToPaper)
67 |
68 | const cited_results = results
69 | .filter(p => p.num_cited_by > 0 || p.num_citing > 0)
70 |
71 | if (cited_results.length == 0)
72 | return results
73 | else
74 | return cited_results
75 | } else {
76 | return Promise.reject()
77 | }
78 | })
79 | .catch(err => {
80 | handleServiceErr(err)
81 | return Promise.reject()
82 | })
83 | }
84 |
85 | function convertOAPaperToPaper(p: OAPaper): Paper {
86 | try {
87 | const newP = {
88 | id: trimOAUrl(p.id),
89 | title: p.title || 'NA',
90 | author: p.authorships.map(convertOAAuthorToAuthor),
91 | published_year: p.publication_year || 1900,
92 | journal: p.primary_location?.source?.display_name,
93 | num_cited_by: p.cited_by_count,
94 | num_citing: p.referenced_works.length,
95 | doi: p.doi,
96 | pages: p.biblio.first_page + '-' + p.biblio.last_page,
97 | volume: p.biblio.volume
98 | }
99 | return newP
100 | } catch (e) {
101 | console.log(e)
102 | throw e
103 | }
104 | }
105 |
106 | function convertOAAuthorToAuthor(a: OAAuthorship, index: number): Author {
107 | return {
108 | author_id: a.author.id ? parseInt(trimOAUrl(a.author.id).slice(1)) : undefined,
109 | name: a.author.display_name,
110 | institution:
111 | !a.institutions || a.institutions.length == 0
112 | ? undefined
113 | : {
114 | id: !a.institutions[0].id
115 | ? undefined
116 | : parseInt(trimOAUrl(a.institutions[0].id).slice(1)),
117 | name: a.institutions[0].display_name
118 | },
119 | sequence: index
120 | }
121 | }
122 |
123 | function trimOAUrl(url: string): string {
124 | return url.replace('https://openalex.org/', '')
125 | }
126 |
127 | export function searchOAAutocomplete(
128 | query: string
129 | ): Promise {
130 | if (query) {
131 | return axios
132 | .get(
133 | `https://api.openalex.org/autocomplete/works?q=${encodeURIComponent(
134 | query
135 | )}&mailto=info@inciteful.xyz`
136 | )
137 | .then((res: AxiosResponse) => {
138 | if (res.data && res.data.results && res.data.results.length > 0) {
139 | return res.data.results.map(convertOAAutocomplete)
140 | } else {
141 | return Promise.reject()
142 | }
143 | })
144 | .catch(err => {
145 | handleServiceErr(err)
146 | return Promise.reject()
147 | })
148 | }
149 | return Promise.resolve([])
150 | }
151 |
152 | function convertOAAutocomplete(p: OAAutosuggestResult): PaperAutosuggest {
153 | return {
154 | id: trimOAUrl(p.id),
155 | title: p.display_name,
156 | authors: p.hint,
157 | num_cited_by: p.cited_by_count
158 | }
159 | }
160 |
161 | export function getOAPaper(id: string): Promise {
162 | if (id) {
163 | return axios
164 | .get(
165 | `https://api.openalex.org/works/${encodeURIComponent(
166 | id
167 | )}?mailto=info@inciteful.xyz`
168 | )
169 | .then((res: AxiosResponse) => {
170 | return res.data
171 | })
172 | .catch(err => {
173 | if (err.response && err.response.status == 404) {
174 | return Promise.resolve(undefined)
175 | }
176 |
177 | handleServiceErr(err)
178 | return Promise.reject()
179 | })
180 | }
181 |
182 | return Promise.resolve(undefined)
183 | }
184 |
185 | export function getOAPapers(ids: string[]): Promise {
186 | //example endpoint: https://api.openalex.org/works?filter=openalex:W4224016882|W4223895588
187 | if (ids && ids.length > 0) {
188 | return axios
189 | .get(
190 | `https://api.openalex.org/works?filter=openalex:${ids
191 | .map(id => `${id}`)
192 | .join('|')}&mailto=info@inciteful.xyz`
193 | )
194 | .then((res: AxiosResponse) => {
195 | return res.data.results
196 | })
197 | .catch(err => {
198 | handleServiceErr(err)
199 | return Promise.reject()
200 | })
201 | }
202 |
203 | return Promise.resolve([])
204 | }
205 |
--------------------------------------------------------------------------------
/src/utils/options.ts:
--------------------------------------------------------------------------------
1 | const pruneKey = 'pruneLevel'
2 |
3 | function getPruneLevel (): number | undefined {
4 | const val = parseInt(localStorage[pruneKey])
5 | return val || undefined
6 | }
7 |
8 | function setPruneLevel (val: number | undefined) {
9 | if (val) {
10 | localStorage.setItem(pruneKey, val.toString())
11 | } else {
12 | localStorage.removeItem(pruneKey)
13 | }
14 | }
15 |
16 | export default {
17 | getPruneLevel,
18 | setPruneLevel
19 | }
20 |
--------------------------------------------------------------------------------
/src/utils/pagedata.ts:
--------------------------------------------------------------------------------
1 | const DEFAULT_TITLE = 'Inciteful'
2 |
3 | function setTitle (title: string) {
4 | document.title = (title || DEFAULT_TITLE) + ' | Inciteful.xyz'
5 | }
6 | function setDescription (description: string) {
7 | if (document != null) {
8 | const d = document.querySelector('meta[name="description"]')
9 |
10 | if (d != null) {
11 | d.setAttribute('content', description)
12 | }
13 | }
14 | }
15 |
16 | export default {
17 | setTitle,
18 | setDescription
19 | }
20 |
--------------------------------------------------------------------------------
/src/utils/sql.ts:
--------------------------------------------------------------------------------
1 | interface SqlFilter {
2 | sql: string,
3 | default?: number | undefined
4 | }
5 |
6 | const filterMap: Record = {
7 | minYear: {
8 | sql: 'p.published_year >= {}'
9 | },
10 | maxYear: {
11 | sql: 'p.published_year <= {}'
12 | },
13 | minDistance: {
14 | sql: 'p.distance >= {}',
15 | default: 1
16 | },
17 | maxDistance: {
18 | sql: 'p.distance <= {}'
19 | },
20 | keywords: {
21 | sql: 'p.paper_id IN (SELECT paper_id FROM title_search(\'{}\'))'
22 | }
23 | }
24 |
25 | function getSqlForKey(k: string, value: string | undefined) {
26 | const val = value || filterMap[k].default
27 |
28 | if (val != undefined) {
29 | return filterMap[k].sql.replace('{}', val.toString())
30 | }
31 |
32 | return undefined
33 | }
34 |
35 | function constructFilterSql(filters: Record) {
36 | const sqlArray = Object.keys(filterMap)
37 | .map((k) => getSqlForKey(k, filters[k]))
38 | .filter((v) => v)
39 |
40 | return sqlArray.join('\nAND ')
41 | }
42 |
43 | function addFilters(sql: string, filters: Record) {
44 | while (sql.includes('{{filters}}')) {
45 | sql = _addFilters(sql, filters)
46 | }
47 |
48 | return sql
49 | }
50 | function _addFilters(sql: string, filters: Record) {
51 | if (!sql) {
52 | return sql
53 | }
54 |
55 | const pos = sql.indexOf('{{filters}}')
56 |
57 | if (pos === -1) {
58 | return sql
59 | }
60 |
61 | const filtSql = constructFilterSql(filters)
62 |
63 | return sql.replace('{{filters}}', filtSql)
64 | }
65 |
66 | export default {
67 | addFilters
68 | }
69 |
--------------------------------------------------------------------------------
/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | About Inciteful
4 |
5 | The seed of the idea for Inciteful came about as a result of a frustrating
6 | experience after helping my wife (the academic of the family) with some
7 | research. I was interested in a topic that was tangentially related to
8 | what she was working on but she wasn't familiar with that specific
9 | literature. As a result I went down a rabbit hole of going to Google
10 | Scholar to look for papers, scanning papers for references, looking at who
11 | cited those papers, and then returning to Google Scholar to repeat the
12 | process. All while keeping the results of my search in a notepad, noting
13 | connections between different papers to better understand how they are all
14 | related. After an embarrassingly long time, I realized I was just drawing
15 | a network graph. Probably an experience that most academics can relate to.
16 |
17 |
18 | My background is in Computer Science and am a developer by trade so my
19 | mind quickly jumped to how this whole process could be automated. I also
20 | have a bit of ADD so the interest in my original research question was
21 | quickly and easily overtaken by this new project.
22 |
23 |
24 | After spending the first year of life as a hacked together python notebook
25 | using the apis from
26 | Open Citations
31 | and
32 | Crossref , I decided to try and scale it up to something that others could use.
37 |
38 |
39 | The biggest problem was the real time creation of the graph. My original
40 | solution took over 15 minutes to build the graph for a paper with only a
41 | few citations and 40-50 references. It was clearly too slow. After a
42 | number of fits and starts, where I ended up learning a new programming
43 | language (Rust ), testing out a number of databases, changing the source of my data, and
48 | re-architecting the code a few times, I finally found a solution that I'm
49 | happy with and I hope you are too.
50 |
51 |
52 | As this is built upon the back of data that I received for free, it feels
53 | like bad karma to charge for the service. That being said, it's pretty
54 | expensive to keep the site up and running ($500/month) as the backend
55 | servers need to be pretty powerful to give a good experience to the end
56 | users. In the future I might start accepting donations to cover the cost
57 | of running the site or if you know of an institution who might be
58 | interested in sponsoring the project, an introduction would be
59 | appreciated.
60 |
61 |
62 | It's still under active development so if you have any questions,
63 | suggestions or just want to say hi, please feel free to send me your
64 | feedback .
69 |
70 |
71 |
72 |
81 |
82 | SingleColumn
83 |
--------------------------------------------------------------------------------
/src/views/BetaFeatures.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Beta Features
4 |
5 | Below you will find a list of the current features we are beta testing. I
6 | cannot promise these will be supported for any significant period of time
7 | or that they will ever see the light of day. I try to ensure that state is
8 | preserved in the url for a page, that means if you share the url, the
9 | person you share it with should see the exact same page as you. These
10 | features break that promise. Use at your own risk.
11 | Feedback
16 | on these features is greatly appreciated.
17 |
18 | Graph Pruning
19 |
20 | In most fields there are papers which are considered "canonical" or are
21 | methods/tools papers. These papers tend to be cited by everyone in the
22 | field and their presence doesn't provide useful information to the graph.
23 | In fact, in many instances these papers create their own gravity and most
24 | graphs in the field end up centering around them, making the graph less
25 | useful.
26 |
27 |
28 | When you use this feature, if a paper has more than the specified number
29 | of citations, then the graph builder will not pull in papers which cite
30 | that paper. The paper will still be present in the graph and it will still
31 | get citations if other papers in the graph cite it. The net effect is to
32 | dampen the papers impact on the overall graph. Please feel free to
33 | experiment with different levels. I reccomend starting high (5,000 or so)
34 | and working your way down to what feels right.
35 |
36 |
37 | When giving feedback about what works for you, please specify the prune
38 | level, your field of study, and some example urls.
39 |
40 |
41 |
Prune Level
44 |
45 |
52 |
53 |
54 |
55 |
56 |
57 | Save
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | Preferences Successfully Saved
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
113 |
--------------------------------------------------------------------------------
/src/views/DataSources.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
Data Sources
7 |
8 | Without all of the work that has gone into coordinating, parsing,
9 | and categorizing all of the academic literature undertaken by a
10 | variety of different projects, Inciteful, and other sites like it,
11 | wouldn't exist. We have pulled both data and/or inspiration from all
12 | of the projects listed below:
13 |
14 |
15 |
16 |
19 |
20 | OpenAlex is a free and open catalog of the world's scholarly papers,
21 | researchers, journals, and institutions — along with all the ways
22 | they're connected to one another. Using OpenAlex, you can build your
23 | own scholarly search engine, recommender service, or knowledge
24 | graph. You can help manage research by tracking citation impact,
25 | spotting promising new research areas, and identifying and promoting
26 | work from underrepresented groups. And you can do research on
27 | research itself, in areas like bibliometrics, science and technology
28 | studies, and Science of science policy. Because we think all
29 | research should be free and open, OpenAlex is free and open itself,
30 | and we're built on a fully Open Source codebase. We believe the
31 | global research system is one of humankind's most beautiful
32 | creations. OpenAlex aims to make that whole beautiful creation
33 | available to everyone, everywhere.
34 |
35 |
- (OpenAlex)
38 |
39 |
40 |
41 |
46 |
47 | Semantic Scholar is a project developed at the Allen Institute for
48 | Artificial Intelligence. Publicly released in November 2015, it is
49 | designed to be an AI-backed search engine for academic publications.
50 | The project uses a combination of machine learning, natural language
51 | processing, and machine vision to add a layer of semantic analysis
52 | to the traditional methods of citation analysis, and to extract
53 | relevant figures, entities, and venues from papers. In comparison to
54 | Google Scholar and PubMed, Semantic Scholar is designed to highlight
55 | the most important and influential papers, and to identify the
56 | connections between them.
57 |
- (Wikipedia)
62 |
63 |
64 |
65 |
67 |
68 |
69 | Crossref makes research outputs easy to find, cite, link, assess,
70 | and reuse. We’re a not-for-profit membership organization that
71 | exists to make scholarly communications better.
72 |
73 |
74 |
75 |
80 |
81 |
82 | OpenCitations is an independent infrastructure organization for open
83 | scholarship dedicated to the publication of open bibliographic and
84 | citation data by the use of Semantic Web (Linked Data) technologies.
85 | It is also engaged in advocacy for open citations, particularly in
86 | its role as a key founding member of the Initiative for Open
87 | Citations (I4OC).
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
106 |
107 | SingleColumn
108 |
--------------------------------------------------------------------------------
/src/views/Export.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Export to
7 |
8 | Zotero
9 | Mendeley
10 |
11 |
12 |
13 |
14 |
15 |
16 | You first install the
17 | Zotero Connector . Then you can import the papers below using the extension.
19 |
20 |
21 |
22 |
23 |
24 | You must install the
25 | Mendeley Web Importer . Then you can import the papers below using the
27 | extension.
28 |
29 |
30 |
31 |
32 |
38 |
39 |
40 |
79 |
--------------------------------------------------------------------------------
/src/views/LitReview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
84 |
--------------------------------------------------------------------------------
/src/views/LitReviewQuery.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
28 |
--------------------------------------------------------------------------------
/src/views/NotFound.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Page Not Found
4 |
5 |
6 |
15 |
16 | SingleColumn
17 |
--------------------------------------------------------------------------------
/src/views/PaperDiscovery.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Click on the purple plus signs (
20 | ) to add the most interesting papers to the
21 | graph.
22 | We recommend
23 | that you add at least five papers to the graph in order to
24 | find the most relevant results. If the papers below don't seem
25 | relevant, use the keyword filter to find the ones which
26 | are.
27 |
28 |
29 |
38 | Learn More →
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | We could not construct the graph. We don't have records of any
54 | references or citations for this paper, which makes it kind of hard.
55 | Please search for another paper.
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
162 |
--------------------------------------------------------------------------------
/src/views/PaperDiscoveryQuery.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
70 |
--------------------------------------------------------------------------------
/src/views/Search.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Search Results for "{{ query }}"
5 |
6 |
7 |
10 |
11 |
12 |
13 | Use the button to add multiple papers to
14 | the graph. You can also use the "View Graph" button or click
15 | on the title to go directly to that paper's graph.
16 |
17 |
18 |
19 |
20 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
80 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ['./src/**/*.html', './src/**/*.vue'],
3 | theme: {
4 | extend: {
5 | animation: {
6 | 'bounce-fade': 'bounce 5s 5s'
7 | }
8 | }
9 | },
10 | plugins: [require('@tailwindcss/forms')]
11 | }
12 |
--------------------------------------------------------------------------------
/tests/unit/example.spec.ts:
--------------------------------------------------------------------------------
1 | // import { shallowMount } from '@vue/test-utils'
2 | // import HelloWorld from '@/components/HelloWorld.vue'
3 |
4 | // describe('HelloWorld.vue', () => {
5 | // it('renders props.msg when passed', () => {
6 | // const msg = 'new message'
7 | // const wrapper = shallowMount(HelloWorld, {
8 | // propsData: { msg }
9 | // })
10 | // expect(wrapper.text()).toMatch(msg)
11 | // })
12 | // })
13 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "strictPropertyInitialization": false,
13 | "resolveJsonModule": true,
14 | "sourceMap": true,
15 | "baseUrl": ".",
16 | "types": ["webpack-env"],
17 | "paths": {
18 | "@/*": ["src/*"]
19 | },
20 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
21 | },
22 | "include": [
23 | "src/**/*.ts",
24 | "src/**/*.tsx",
25 | "src/**/*.vue",
26 | "tests/**/*.ts",
27 | "tests/**/*.tsx"
28 | ],
29 | "exclude": ["node_modules"]
30 | }
31 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | chainWebpack: config => {
3 | config.module
4 | .rule('yaml')
5 | .test(/\.yaml$/)
6 | .use('js-yaml-loader')
7 | .loader('js-yaml-loader')
8 | .end()
9 | },
10 | configureWebpack: {
11 | devtool: 'source-map'
12 | }
13 | }
14 |
--------------------------------------------------------------------------------