diff --git a/.babelrc b/.babelrc
deleted file mode 100644
index 389310f..0000000
--- a/.babelrc
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "exclude": "node_modules/**",
- "presets": [
- "@babel/preset-flow"
- ],
- "sourceMap": false
-}
\ No newline at end of file
diff --git a/.dockerignore b/.dockerignore
index 802ddd2..fe7e9f5 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,3 +1,6 @@
node_modules
-npm-debug.log
+storybook-static
+build
dist
+Dockerfile
+.dockerignore
diff --git a/.eslintrc.js b/.eslintrc.js
deleted file mode 100644
index abdc5ea..0000000
--- a/.eslintrc.js
+++ /dev/null
@@ -1,23 +0,0 @@
-module.exports = {
- parser: 'babel-eslint',
- plugins: ['flowtype'],
- env: {
- browser: true,
- es6: true,
- },
- extends: 'plugin:flowtype/recommended',
- parserOptions: {
- ecmaVersion: 2017,
- sourceType: 'module',
- },
- rules: {
- indent: ['error', 2, { SwitchCase: 1 }],
- quotes: ['error', 'double', { avoidEscape: true }],
- semi: ['error', 'always'],
- },
- settings: {
- flowtype: {
- onlyFilesWithFlowAnnotation: false,
- },
- },
-};
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..57e95e1
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,32 @@
+{
+ "extends": ["react-app"],
+ "ignorePatterns": [
+ "dist/**",
+ "build/**",
+ "storybook-static/**",
+ "!.storybook"
+ ],
+ "rules": {
+ "no-unused-vars": "off"
+ },
+ "overrides": [
+ {
+ "files": ["**/*.ts?(x)"],
+ "rules": {
+ "@typescript-eslint/no-unused-vars": [
+ "warn",
+ {
+ "argsIgnorePattern": "^_",
+ "varsIgnorePattern": "^_"
+ }
+ ]
+ }
+ },
+ {
+ "files": ["**/*.stories.*"],
+ "rules": {
+ "import/no-anonymous-default-export": "off"
+ }
+ }
+ ]
+}
diff --git a/.flowconfig b/.flowconfig
deleted file mode 100644
index c57b832..0000000
--- a/.flowconfig
+++ /dev/null
@@ -1,4 +0,0 @@
-[include]
-
-[ignore]
-.*/node_modules/flow-bin
\ No newline at end of file
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..5cebdac
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,13 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: ilyvion # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
new file mode 100644
index 0000000..360167f
--- /dev/null
+++ b/.github/workflows/CI.yml
@@ -0,0 +1,46 @@
+name: CI
+
+on:
+ push:
+ branches: [develop]
+ pull_request:
+ branches: [develop]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Use Node.js 18.x
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18.x
+ cache: 'npm'
+
+ - name: Install Node modules
+ run: npm ci
+
+ - name: Run ESLint
+ run: npx eslint .
+
+ # Tests are currently kind of broken. Leave it for another day.
+
+ #- name: Run tests
+ # run: npm test
+
+ # - name: Install Playwright
+ # run: npx playwright install --with-deps
+
+ # - name: Build Storybook
+ # run: npm run build-storybook
+
+ # - name: Run Storybook tests
+ # run: |
+ # npx concurrently -k -s last -n "SB,TEST" -c "magenta,blue" \
+ # "npx http-server storybook-static --port 6006 --silent" \
+ # "npx wait-on http://127.0.0.1:6006/ && npm run test-storybook"
+
+ - name: Ensure everything builds
+ run: npm run build
diff --git a/.github/workflows/draft_new_release.yml b/.github/workflows/draft_new_release.yml
new file mode 100644
index 0000000..0e25309
--- /dev/null
+++ b/.github/workflows/draft_new_release.yml
@@ -0,0 +1,62 @@
+name: 'Draft new release'
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'The version you want to release.'
+ required: true
+
+jobs:
+ draft-new-release:
+ name: 'Draft a new release'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Create release branch
+ run: git checkout -b release/${{ github.event.inputs.version }}
+
+ - name: Update changelog
+ uses: thomaseizinger/keep-a-changelog-new-release@1.1.0
+ with:
+ version: ${{ github.event.inputs.version }}
+
+ # In order to make a commit, we need to initialize a user.
+ - name: Initialize mandatory git config
+ run: |
+ git config user.name "GitHub Actions"
+ git config user.email noreply@github.com
+
+ # This step will differ depending on your project setup
+ # Fortunately, yarn has a built-in command for doing this!
+ - name: Bump version in package.json
+ run: npm version ${{ github.event.inputs.version }} --no-git-tag-version
+
+ - name: Commit changelog and manifest files
+ id: make-commit
+ run: |
+ git add CHANGELOG.md package.json
+ git commit --message "chore: prepare release ${{ github.event.inputs.version }}"
+
+ echo "::set-output name=commit::$(git rev-parse HEAD)"
+
+ - name: Push new branch
+ run: git push origin release/${{ github.event.inputs.version }}
+
+ - name: Create pull request
+ uses: thomaseizinger/create-pull-request@1.0.0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ head: release/${{ github.event.inputs.version }}
+ base: main
+ title: Release version ${{ github.event.inputs.version }}
+ reviewers: ${{ github.actor }}
+ body: |
+ Hi @${{ github.actor }}!
+
+ This PR was created in response to a manual trigger of the release workflow here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}.
+ I've updated the changelog and bumped the versions in the manifest files in this commit: ${{ steps.make-commit.outputs.commit }}.
+
+ Merging this PR will create a GitHub release and upload any assets that are created as part of the release build.
diff --git a/.github/workflows/publish_new_release.yml b/.github/workflows/publish_new_release.yml
new file mode 100644
index 0000000..415cc95
--- /dev/null
+++ b/.github/workflows/publish_new_release.yml
@@ -0,0 +1,78 @@
+name: 'Publish new release'
+
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - closed
+
+jobs:
+ release:
+ name: Publish new release
+ runs-on: ubuntu-latest
+ # only merged pull requests that begin with 'release/' or 'hotfix/' must trigger this job
+ if: github.event.pull_request.merged == true &&
+ (startsWith(github.event.pull_request.head.ref, 'release/') || startsWith(github.event.pull_request.head.ref, 'hotfix/'))
+
+ steps:
+ - name: Extract version from branch name (for release branches)
+ if: startsWith(github.event.pull_request.head.ref, 'release/')
+ run: |
+ BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
+ VERSION=${BRANCH_NAME#release/}
+
+ echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
+
+ - name: Extract version from branch name (for hotfix branches)
+ if: startsWith(github.event.pull_request.head.ref, 'hotfix/')
+ run: |
+ BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
+ VERSION=${BRANCH_NAME#hotfix/}
+
+ echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
+
+ - uses: actions/checkout@v3
+
+ - name: Use Node.js 18.x
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18.x
+ cache: 'npm'
+
+ - name: Install Node modules
+ run: npm ci
+
+ - name: Build scripts
+ run: npm run build-userscript
+
+ - name: Extract release notes
+ id: extract-release-notes
+ uses: ffurrer2/extract-release-notes@v1
+ with:
+ changelog_file: CHANGELOG.md
+
+ - name: Release
+ uses: softprops/action-gh-release@v1
+ with:
+ target_commitish: ${{ github.event.pull_request.merge_commit_sha }}
+ tag_name: ${{ env.RELEASE_VERSION }}
+ name: ${{ env.RELEASE_VERSION }}
+ draft: false
+ prerelease: false
+ body: ${{ steps.extract-release-notes.outputs.release_notes }}
+ files: |
+ ./dist/qc-ext.user.js
+ ./dist/qc-ext.meta.js
+
+ - name: Merge main into dev branch
+ uses: thomaseizinger/create-pull-request@1.0.0
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ head: main
+ base: develop
+ title: Merge main into develop branch
+ body: |
+ This PR merges the main branch back into develop.
+ This happens to ensure that the updates that happend on the release branch, i.e. CHANGELOG and manifest updates are also present on the dev branch.
diff --git a/.gitignore b/.gitignore
index 0485a72..e109356 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,30 @@
-/.sass-cache
-/assets/generated
-/assets/removeFlow
-/dist
-/nbproject/private
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
/node_modules
-/.vagrant
-/.tmp
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# deployment
+/dist
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+# storybook
+/src/stories
+/storybook-static
diff --git a/.husky/commit-msg b/.husky/commit-msg
new file mode 100644
index 0000000..5426a93
--- /dev/null
+++ b/.husky/commit-msg
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npx commitlint --edit $1
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..36af219
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,4 @@
+#!/bin/sh
+. "$(dirname "$0")/_/husky.sh"
+
+npx lint-staged
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..521a9f7
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+legacy-peer-deps=true
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..21d1351
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+node_modules
+
+# Ignore artifacts:
+build
+coverage
\ No newline at end of file
diff --git a/.prettierrc.js b/.prettierrc.js
deleted file mode 100644
index e2bd18a..0000000
--- a/.prettierrc.js
+++ /dev/null
@@ -1,5 +0,0 @@
-// prettier.config.js or .prettierrc.js
-module.exports = {
- tabWidth: 2,
- singleQuote: false,
-};
diff --git a/.prettierrc.json b/.prettierrc.json
new file mode 100644
index 0000000..a7f5cab
--- /dev/null
+++ b/.prettierrc.json
@@ -0,0 +1,9 @@
+{
+ "trailingComma": "es5",
+ "tabWidth": 4,
+ "semi": false,
+ "singleQuote": true,
+ "importOrder": ["\\.css$", "^@", "^~", "^[./]"],
+ "importOrderSeparation": true,
+ "importOrderSortSpecifiers": true
+}
diff --git a/.storybook/main.ts b/.storybook/main.ts
new file mode 100644
index 0000000..2fe31f5
--- /dev/null
+++ b/.storybook/main.ts
@@ -0,0 +1,36 @@
+import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin'
+
+import type { StorybookConfig } from '@storybook/core-common'
+
+const config: StorybookConfig = {
+ stories: [
+ '../src/**/*.stories.mdx',
+ '../src/**/*.stories.@(js|jsx|ts|tsx)',
+ ],
+ addons: [
+ '@storybook/addon-links',
+ '@storybook/addon-essentials',
+ '@storybook/addon-interactions',
+ '@storybook/preset-create-react-app',
+ ],
+ framework: '@storybook/react',
+ core: {
+ builder: '@storybook/builder-webpack5',
+ },
+ webpackFinal: async (config, { configType: _configType }) => {
+ if (!config.resolve) {
+ config.resolve = {}
+ }
+ if (!config.resolve.plugins) {
+ config.resolve.plugins = []
+ }
+ config.resolve.plugins.push(new TsconfigPathsPlugin())
+
+ return config
+ },
+ features: {
+ interactionsDebugger: true,
+ },
+}
+
+export default config
diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html
new file mode 100644
index 0000000..d0a16c4
--- /dev/null
+++ b/.storybook/preview-body.html
@@ -0,0 +1,2 @@
+
+
diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html
new file mode 100644
index 0000000..b3812ce
--- /dev/null
+++ b/.storybook/preview-head.html
@@ -0,0 +1,5 @@
+
diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx
new file mode 100644
index 0000000..d051864
--- /dev/null
+++ b/.storybook/preview.tsx
@@ -0,0 +1,74 @@
+// For whatever reason, Typescript can't find things from this file;
+// at least not as far as vscode is concerned.
+// I can't figure out why, though, so for now, this file will appear
+// to have errors, even though it doesn't really.
+//
+import { StartOptions, rest, setupWorker } from 'msw'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { ToastContainer } from 'react-toastify'
+
+import './qc.css'
+import 'react-toastify/dist/ReactToastify.min.css'
+import '~/index.css'
+
+import { setSettings } from '@store/settingsSlice'
+import store from '@store/store'
+
+import Settings from '~/settings'
+import { setup } from '~/utils'
+
+setup(true)
+
+export const parameters = {
+ actions: { argTypesRegex: '^on[A-Z].*' },
+ controls: {
+ matchers: {
+ color: /(background|color)$/i,
+ date: /Date$/,
+ },
+ },
+}
+
+store.dispatch(setSettings(Settings.DEFAULTS))
+
+export const decorators = [
+ (Story) => (
+
+
+
+
+ ),
+]
+
+// Storybook executes this module in both bootstrap phase (Node)
+// and a story's runtime (browser). However, we cannot call `setupWorker`
+// in Node environment, so we need to check if we're in a browser.
+if (typeof global.process === 'undefined') {
+ // Create the mockServiceWorker (msw).
+ const worker = setupWorker()
+ // Start the service worker.
+ const options: StartOptions = {
+ onUnhandledRequest(req, print) {
+ if (!req.url.href.startsWith('http://localhost:3000/api/')) {
+ return
+ }
+
+ print.warning()
+ },
+ }
+ if (process.env.NODE_ENV === 'production') {
+ const pathname = window.location.pathname
+ const pathbasename = pathname.substring(
+ 0,
+ pathname.lastIndexOf('/') + 1
+ )
+ options.serviceWorker = {
+ url: `${pathbasename}mockServiceWorker.js`,
+ }
+ }
+ window.mswStart = worker.start(options)
+ // Make the `worker` and `rest` references available globally,
+ // so they can be accessed in stories.
+ window.msw = { worker, rest }
+}
diff --git a/.storybook/qc.css b/.storybook/qc.css
new file mode 100644
index 0000000..aafedb3
--- /dev/null
+++ b/.storybook/qc.css
@@ -0,0 +1,2 @@
+@import 'https://questionablecontent.net/css/foundation.css';
+@import 'https://questionablecontent.net/css/app.css';
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index dfe90df..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-language: node_js
-node_js:
-- 8.4.0
-before_install:
-- npm install -g grunt-cli
-- rvm install 2.4.2
-- gem install sass --version "=3.4.25"
-- gem install compass --version "=1.0.3"
-install:
-- npm update -g
-- npm install
-script:
-- grunt
-- npm run print-userscript-banner
-deploy:
- provider: releases
- api_key:
- secure: UfVMZo8geJjIw3p9p2Iw5LDXWXVcJKNBkAam8PDDWlqprPhRuHsvvvev5BGchjZ8mlr/3R2poYn+6KXWqiGNh/1/4vEx1GCeo9/Y7IdZw4Zk11Z0rZIsg0G8VyQidLwEJC/g3y5INV3RkHmg2ux5qHpgWXVaaVInyPgHEYxoVeYtGb1QRQfu45axLjxhT6PbGjSzQ9oa6kE5+mR+GZAfr4KdiVXYVfN+VFF75KMh8swoRUOtxX5ED+mblC83ar25BHUCSwDvkfMZgNJHziTI0KzIJHSoxAZnso14cx6/BWcRW3chsU/Eh5mXT3Z94iGIgrTTNKjq6nDC/HJuRMwVKfMGSkQBOpAtKEGpSusLOsluOw1XVRytnkIXEE5+CnQNS8J0GqKUU9Ea47EScH+VW4xUmWolyGDuYjZC0tH2GFhxcAXDUFMbCxTwVNIuoph7nokxbYAPgGc4g0WeI/fNJ0szHTw9mGFbWXFh4kWj0bCEcKwPBkgrN18n7l1EiM7uR221jUPjqJihS5hiyVyBsjzxefS/lY1+9C0Fb0tBRMWE/jEqgh1LVA5ir8LiRlV3FxHdT04ebR6y5aq5e1A8Xfu+z72aPi8ZmGz4hdU4VHIou6VzzzGuPp9lnQx5UEZ+nvBAPeoprcXG0HV9CwaJa3aStPdSm8WSJxNHdyMUQdQ=
- file:
- - dist/qc-ext.min.user.js
- - dist/qc-ext.user.js
- - dist/qc-ext.meta.js
- skip_cleanup: true
- on:
- repo: Questionable-Content-Extensions/client
- tags: true
diff --git a/.vscode/settings.json b/.vscode/settings.json
index fd1c8f3..324a961 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,4 @@
{
- "javascript.validate.enable": false
-}
\ No newline at end of file
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.formatOnSave": true
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..cd01b80
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,353 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased][unreleased]
+
+## [1.0.0][] - 2023-09-09
+
+> Hello! It has once again been a while, but this time, it's a big one! I've been meaning to do it for a long time, but I finally got around to rewrite the extension from scratch using React as the framework rather than AngularJS v1. While I was at it, I also updated/upgraded everything else about the script that I could, and it's much more modern and user friendly now.
+>
+> Since this is a rewrite, the change log below will be mostly _added_ features rather than changed or fixed ones. Old features that have been migrated into the new version won't be mentioned unless changed significantly in the rewrite.
+
+### Added ✨
+
+- Add Storybookjs
+- Added `GoToComicDialog` for choosing a specific comic from a list
+- Added ko-fi donation link in bottom of `SettingsDialog`
+
+### Changed 🔧
+
+- In coordination with the server, the API has been optimized to transfer a lot less data in each request.
+- Make `debug` logging dynamic rather than chosen at startup
+
+### Fixed 🐛
+
+- Make `shortcut` take-over code work properly everywhere
+
+### Removed 🗑
+
+- Removed small/large ribbon setting; ribbon is always small now.
+
+## [0.6.2][] - 2022-08-31
+
+> It's been a while! And this release isn't really one of improvement, but rather necessity. For the longest time, the Questionable Content Extensions has used Heroku's free plan as its backend, because this is a hobby project, and I don't really have the means to pay for dedicated hosting and bandwidth for it, as I make no money off this project, and I don't want it to be a financial burden. [Heroku/Salesforce has finally decided to remove their free tier](https://blog.heroku.com/next-chapter), which is neither surprising nor unexpected, but that naturally means that I have to use a different solution.
+>
+> For the time being, I'm going to go back to self-hosting, as I did before I discovered Heroku and switched to it. We'll just have to see whether it makes an impact on my costs or not. In a more positive turn of events, the self-hosted server appears to have much better performance than Heroku did, so that's a plus.
+>
+> Just in case someone's feeling generous or want to demonstrate gratitude for this thing I've made, I'm just going to leave this here as well. 😊
+>
+> [![Buy Me a Coffee at ko-fi.com](https://cdn.ko-fi.com/cdn/kofi2.png?v=3 'height:36px')](https://ko-fi.com/ilyvion)
+
+### Changed 🔧
+
+- Move to Docker from Vagrant
+- Format files according to Prettier standard.
+- Better handling of error communicating with server
+- Accept colors with and without `#` prefix
+- Run `dist` with the build script
+- Change link from Heroku to local server
+- Update copyright end year
+
+### Fixed 🐛
+
+- Fix field spelling
+
+## [0.6.1][] - 2019-12-08
+
+### Fixed 🐛
+
+- Better fix for missing sidebar. More idiomatic fix for Issue #37. It uses jQuery within the proper file, just before adding the sidebar base. It also directly tests the DOM to see if the "small-2" column is missing, rather than just guessing using the URL. Fixes #37
+
+## [0.6.0][] - 2019-03-08
+
+> This release, like the previous one, has been mostly about code quality and features for editors and me, the developer. I'm hoping that I can now start putting in more features for the end-user as we move towards version 0.7. If you have ideas for new features, or you have any problems with the extension as it is, don't hesitate to [tell us about them][issues]! While I have quite a lot of ideas left to implement, I'd also love to implement ideas that aren't just my own.
+
+### Added ✨
+
+- Add support for the new item image system and for image uploading
+- Add edit log view for editors
+- Add flags for indicating whether a comic is lacking certain features
+- Add loading indicators for the comic image. Implements the comic image of #14.
+- Add updating indicators for the item details dialog
+- Add updating indicators and/or disable controls for editor actions
+- Add loading indicators for edit log. Closes #14
+- Add ItemService to avoid loading data multiple times
+- Report version to server. Closes #29
+- Create LICENSE
+
+### Changed 🔧
+
+- Use async where possible
+- Do events for loading item data
+- Supports using the correct image format data from the server, which closes #17
+- Update copyright year
+- Rename ComicDataControllerBase to EventHandlingControllerBase
+- Separate out code checking from building. To speed up building simple fixes and amendments (Flow and ESLint are slow)
+- Move more code to ItemService, where it belongs. Closes #31
+- Organize the existing CSS
+- Extract inline styles to proper style classes. Closes #32
+- Update copyright year also for HTML templates
+- Update build packages
+- Fully transition to using events for maintenance mode
+
+### Fixed 🐛
+
+- Fix various minor bugs
+- Ensure maintenance mode is handled correctly everywhere. Closes #27
+- Handle errors at all in the edit log dialog
+- Set $inject on SettingsController so the code keeps working uglified/minified
+- Ensure that random comic navigation respects exclusion settings. Closes #18
+- Fix indentation in constants.js
+- Undo checkboxes and turn off update indicator when updates fail
+- Fix server/client API mismatch
+- Due to requiring generated assets, building must happen before checking
+
+## [0.5.3][] - 2019-03-01
+
+### Added ✨
+
+- Added upgrade path for settings saved with GM4 shim
+
+### Changed 🔧
+
+- Replaced JSHint and JSCS with ESLint
+- Upgraded codebase to use ES6 modules instead of implied file order and allow using up to ES2017 features
+- Begin transition to using Flow for type checking
+- Switch to new server address
+- Use Flow
+- Refactor code to use classes
+- Improve build system
+
+### Fixed 🐛
+
+- Fix change log version comparison issue
+- (Attempted to) Fix Travis build
+- Fix issue where settings weren't being saved
+
+### Removed 🗑
+
+- Removed GM4 shim now that we're using native GM4 functions
+
+## [0.5.2][] - 2018-10-03
+
+### Added ✨
+
+- Add shim to support GM 4.0 (PR #23)
+
+### Changed 🔧
+
+- Turn off maximum line length code style requirement
+- Don't reinvent the wheel — use Angular's date formatting. Also, since the date formatting is now done at the template level rather than on comic data load, changing the 12h/24h time setting doesn't require a full refresh of the comic data in order to reformat.
+- Set approximateDate to false during load to hide "(Approximately)" text
+- Ignore .tmp directory
+- Improve script installation/update change log handling
+
+### Fixed 🐛
+
+- Fix build errors
+- Fix #25
+
+## [0.5.1][] - 2017-04-08
+
+### Added ✨
+
+- Add support for showing the comic strip publish date
+- Add support for setting the comic strip publish date
+- Add support for approximate publish dates
+- Add a ribbon indicating comic status for non-canon and guest strips. Closes #4
+- Show an indicator when the script is set to development mode
+
+### Changed 🔧
+
+- Have our $http service always ask for JSON data
+- Deal better with server errors and maintenance
+- Make it possible to hit ENTER to navigate in the comic navigation widget
+- Add change log dialog that shows up on install and update
+
+### Fixed 🐛
+
+- Reduce scope of overzealous border removal style. Fixes #16
+
+### Removed 🗑
+
+- Remove unnecessary CSS logic from allItems header
+
+## [0.5.0][] - 2017-03-30
+
+### Added ✨
+
+- Show locations an item has visited/been shown together with
+- Set up support for using Vagrant to build our user script
+- Add navigation control, which lets you navigate to any specific comic #
+
+### Changed 🔧
+
+- Update Grunt version and Grunt plugin versions
+- Make Travis use Ruby v2.4.1
+- Have VersionEye watch 'develop' branch instead of 'master'
+- Make "Show all members" behave much nicer than before. It now shows the cast/locations/storylines in the comic strip separate from the non-present cast/locations/storylines, and out of the way. (Also, fixes #8 for real this time.)
+
+### Fixed 🐛
+
+- Show all members should always work, even when a comic has no data. Fixes #8
+- Make the button the correct size
+
+## [0.4.1][] - 2016-12-26
+
+### Fixed 🐛
+
+- Fix invalid editor token causing an error in the comic load routine
+
+## [0.4.0][] - 2016-12-26
+
+### Added ✨
+
+- Add in missing notifications for locations, storylines, titles and taglines
+- Put comic number in site title for better browser navigation experience. Resolves #11
+
+### Changed 🔧
+
+- Refactor extra-comic navigation into directive
+- Extract constant for tagline requirement threshold
+- When edit mode is enabled, include editor token on comic data request
+- Update non-color style to work with new page design
+- Refresh comic data when editor mode enabled
+- Changing an item's color now updates the UI immediately
+
+### Fixed 🐛
+
+- Fix create-release script
+- Fix for dynamic news on QC frontpage
+- Fix indentation issues
+- Fix bug introduced regarding editorData from the web service
+- Save settings regardless of how the setting dialog is closed. Fixes #3
+- Fixed #13 and hopefully also fixed #12
+
+### Removed 🗑
+
+- Transfer TODO.txt to issues and delete it
+
+## [0.3.3][] - 2016-08-19
+
+### Changed 🔧
+
+- Update our visual style to better match new page design/layout
+
+## [0.3.2][] - 2016-08-19
+
+### Added ✨
+
+- Allow filtering quick-add dropdown by type of item.
+
+### Fixed 🐛
+
+- Fix issue with getting latest comic # on homepage
+- Fix script to work with new page design/layout
+
+## [0.3.1][] - 2016-03-23
+
+### Fixed 🐛
+
+- Fix issue with next/previous comic calculation when server reports null/unknown
+
+## [0.3.0][] - 2016-03-18
+
+### Added ✨
+
+- Add "friend list" to cast info: Who's seen the most together with whom
+
+### Changed 🔧
+
+- Use navigation data for next and previous comic from the webservice. Previously, the next and previous comic was just simple arithmetic, but this new method ensures that the next and previous comics respect the "skip guest" and "skip non-canon" settings.
+- Show different messages for different kinds of items
+
+### Fixed 🐛
+
+- Don't interfere with Firefox' Alt+Left/Alt+Right navigation. Fixes #2.
+- Deal with the two special cases of no next or no previous comic from the web service.
+- Editor mode: Show taglines missing only for comics >= 3133 and missing location
+
+### Removed 🗑
+
+- Remove no longer used page.html template
+
+## [0.2.1][] - 2016-03-13
+
+### Fixed 🐛
+
+- Create special case for Firefox when it comes to using the "shortcut.js" script already in the page because it's not willing to work as you'd expect it to.
+
+### Removed 🗑
+
+- Remove all dependencies on unsafeWindow (found ways around using it)
+
+## [0.2.0][] - 2016-03-13
+
+### Added ✨
+
+- Add some keyboard shortcuts to make edit mode easier to use
+- Add "tagline" support to the comic
+
+### Changed 🔧
+
+- Position the "no image" elements more centrally
+- Better choice of word doesn't imply it's the last time we'll ever see them again
+- Doing some minor user interface improvements before first public release
+- Show proper error message dialog instead of just logging to console
+
+## [0.1.2][] - 2016-03-12
+
+### Added ✨
+
+- Add a developmentMode flag to let you easily switch between development and production server
+- Added item details dialog
+- Added donut/radial graph for showing how many % of comics items have participated in
+- Add script for easily making releases
+
+### Changed 🔧
+
+- Show "Loading..." text when data for the next comic is loading rather than keep showing the old comic's news
+- Made default grunt task be 'build' instead of 'watch'
+- Move 'createTintOrShade' function from comicService to colorService (where it should've been in the first place)
+
+### Fixed 🐛
+
+- Disable left/right directional button navigation in input boxes (mostly only useful for edit mode)
+- Fix item-group-button position issue
+
+## [0.1.0][] - 2016-03-10
+
+### Added ✨
+
+- Initial commit of existing source code
+- Add VersionEye dependency tracking to README.md
+- Update README.md and TODO.txt with information on Firefox issues
+
+### Fixed 🐛
+
+- Attempt to fix Travis build
+
+[0.6.2]: https://github.com/Questionable-Content-Extensions/client/compare/0.6.1...0.6.2
+[0.6.1]: https://github.com/Questionable-Content-Extensions/client/compare/0.6.0...0.6.1
+[0.6.0]: https://github.com/Questionable-Content-Extensions/client/compare/0.5.3...0.6.0
+[0.5.3]: https://github.com/Questionable-Content-Extensions/client/compare/0.5.2...0.5.3
+[0.5.2]: https://github.com/Questionable-Content-Extensions/client/compare/0.5.1...0.5.2
+[0.5.1]: https://github.com/Questionable-Content-Extensions/client/compare/0.5.0...0.5.1
+[0.5.0]: https://github.com/Questionable-Content-Extensions/client/compare/0.4.1...0.5.0
+[0.4.1]: https://github.com/Questionable-Content-Extensions/client/compare/0.4.0...0.4.1
+[0.4.0]: https://github.com/Questionable-Content-Extensions/client/compare/0.3.3...0.4.0
+[0.3.3]: https://github.com/Questionable-Content-Extensions/client/compare/0.3.2...0.3.3
+[0.3.2]: https://github.com/Questionable-Content-Extensions/client/compare/0.3.1...0.3.2
+[0.3.1]: https://github.com/Questionable-Content-Extensions/client/compare/0.3...0.3.1
+[0.3.0]: https://github.com/Questionable-Content-Extensions/client/compare/0.2.1...0.3
+[0.2.1]: https://github.com/Questionable-Content-Extensions/client/compare/0.2.0...0.2.1
+[0.2.0]: https://github.com/Questionable-Content-Extensions/client/compare/0.1.2...0.2.0
+[0.1.2]: https://github.com/Questionable-Content-Extensions/client/compare/0.1.0...0.1.2
+[0.1.0]: https://github.com/Questionable-Content-Extensions/client/releases/tag/0.1.0
+[issues]: https://github.com/Questionable-Content-Extensions/client/issues
+[unreleased]: https://github.com/Questionable-Content-Extensions/client/compare/1.0.0...HEAD
+[1.0.0]: https://github.com/Questionable-Content-Extensions/client/compare/0.6.2...1.0.0
diff --git a/Dockerfile b/Dockerfile
index 36409a2..7f049ec 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,50 +1,31 @@
-FROM ubuntu:focal
+FROM debian:bookworm AS nodejs
-# Set a directory for the app
WORKDIR /app
-# Update APT
-RUN apt-get update
-
-# Install curl
-RUN apt-get install -y curl
-
-# Install node.js v16
-RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
-RUN apt-get install -y nodejs
-
-# Install build-essentials (required for certain npm packages)
-RUN apt-get install -y build-essential
-
-# Install grunt, jsonlint and jshint
-RUN npm install -g grunt
-
-# Install latest stable Ruby
-RUN curl -sSL https://rvm.io/mpapis.asc | gpg --import
-RUN curl -sSL https://rvm.io/pkuczynski.asc | gpg --import
-RUN curl -sSL https://get.rvm.io | bash -s stable --ruby
-
-# Update Gem
-RUN bash -c "source /etc/profile.d/rvm.sh && gem update --system"
-
-# Install compass
-RUN bash -c "source /etc/profile.d/rvm.sh && gem install compass"
-
-# Copy over package*.json files
-COPY package*.json ./
-
-# Install our NPM dependencies
-RUN npm install --legacy-peer-deps
-
-# Preserve package-lock.json
-RUN cp package-lock.json package-lock.json.new
-
-# Copy all the files to the container
-COPY . .
-
-# Restore package-lock.json
-RUN cp package-lock.json.new package-lock.json
-
-# Finally, run the build script on run
-ENV grunt_command default
-CMD ["bash", "-c", "source /etc/profile.d/rvm.sh && grunt ${grunt_command}"]
+# Download and import the Nodesource GPG key
+RUN apt-get update && \
+ apt-get install -y ca-certificates curl gnupg && \
+ apt-get clean
+RUN mkdir -p /etc/apt/keyrings && \
+ curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | \
+ gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
+
+# Make sure we have npm and nodejs
+ENV NODE_MAJOR=18
+RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
+RUN DEBIAN_FRONTEND=noninteractive \
+ apt-get update && \
+ apt-get install nodejs -y && \
+ apt-get clean
+
+# Next, let's run npm install
+COPY package.json package-lock.json ./
+RUN npm install
+# Update browserslist
+#RUN npx browserslist@latest --update-db
+
+# Finally, let's copy over the rest of the stuff
+COPY . ./
+
+# Run npm run
+ENTRYPOINT ["/usr/bin/npm", "run"]
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index 288d26b..0000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-/* global module, require */
-
-const resolve = require("rollup-plugin-node-resolve");
-const commonJs = require("rollup-plugin-commonjs");
-const virtual = require("rollup-plugin-virtual");
-const babel = require("rollup-plugin-babel");
-
-const licenseBanner = require("./licenseBanner");
-const userScriptBanner = require("./userScriptBanner");
-
-const baseFileName = "qc-ext";
-
-module.exports = function (grunt) {
- "use strict";
-
- // Project configuration.
- grunt.initConfig({
- pkg: grunt.file.readJSON("package.json"),
- watch: {
- css: {
- files: ["**/*.sass", "**/*.scss"],
- tasks: ["build"],
- },
- js: {
- files: ["assets/js/**/*.js", "Gruntfile.js"],
- tasks: ["build"],
- },
- templates: {
- files: ["templates/*.*"],
- tasks: ["build"],
- },
- },
- compass: {
- dist: {
- options: {
- sassDir: "assets/sass",
- cssDir: "assets/generated",
- outputStyle: "compressed",
- },
- },
- },
- eslint: {
- target: ["assets/js/**/*.js", "*.js", "scripts/*.js"],
- },
- concat: {
- options: {
- separator: "",
- stripBanners: true,
- },
- variables: {
- src: [
- "assets/templates/variables.pre.template",
- "assets/generated/css.variables.pass1.js",
- "assets/generated/html.variables.pass1.js",
- "assets/templates/variables.post.template",
- ],
- dest: "assets/generated/variables.pass2.js",
- },
- source: {
- options: {
- banner: `${licenseBanner}\n${userScriptBanner}\n`,
- },
- src: ["assets/generated/rollup.js"],
- dest: `dist/${baseFileName}.user.js`,
- },
- },
- uglify: {
- options: {
- banner: licenseBanner + "\n" + userScriptBanner,
- },
- target: {
- files: {
- [`dist/${baseFileName}.min.user.js`]: [
- `dist/${baseFileName}.user.js`,
- ],
- },
- },
- },
- filesToJavascript: {
- css: {
- options: {
- inputFilesFolder: "assets/generated",
- inputFileExtension: "css",
- outputBaseFile: "assets/templates/variables.empty.template",
- outputBaseFileVariable: "variables.css",
- outputFile: "assets/generated/css.variables.pass1.js",
- },
- },
- htmlTemplates: {
- options: {
- inputFilesFolder: "assets/generated",
- inputFileExtension: "html",
- outputBaseFile: "assets/templates/variables.empty.template",
- outputBaseFileVariable: "variables.html",
- outputFile: "assets/generated/html.variables.pass1.js",
- },
- },
- },
- htmlmin: {
- dist: {
- options: {
- removeComments: true,
- collapseWhitespace: true,
- },
- files: {
- "assets/generated/navigation.html":
- "assets/templates/navigation.html",
- "assets/generated/extra.html": "assets/templates/extra.html",
- "assets/generated/extraNav.html": "assets/templates/extraNav.html",
- "assets/generated/settings.html": "assets/templates/settings.html",
- "assets/generated/editComicData.html":
- "assets/templates/editComicData.html",
- "assets/generated/editLog.html": "assets/templates/editLog.html",
- "assets/generated/addItem.html": "assets/templates/addItem.html",
- "assets/generated/setTitle.html": "assets/templates/setTitle.html",
- "assets/generated/setTagline.html":
- "assets/templates/setTagline.html",
- "assets/generated/setPublishDate.html":
- "assets/templates/setPublishDate.html",
- "assets/generated/donut.html": "assets/templates/donut.html",
- "assets/generated/ribbon.html": "assets/templates/ribbon.html",
- "assets/generated/itemDetails.html":
- "assets/templates/itemDetails.html",
- "assets/generated/comicNav.html": "assets/templates/comicNav.html",
- "assets/generated/changeLog.html": "assets/templates/changeLog.html",
- "assets/generated/date.html": "assets/templates/date.html",
- "assets/generated/comic.html": "assets/templates/comic.html",
- },
- },
- },
- rollup: {
- options: {
- pureExternalImports: true,
- plugins: [
- babel(),
- virtual({
- jquery: "export default jQuery",
- angular: "export default angular",
- greasemonkey: "export default GM",
- }),
- resolve({
- browser: true,
- }),
- commonJs(),
- ],
- },
- main: {
- files: {
- "assets/generated/rollup.js": "assets/js/app.js",
- },
- },
- },
- flow: {
- options: {
- server: false,
- },
- files: ["assets/js/**/*.js"],
- },
- babel: {
- options: {
- sourceMap: true,
- },
- dist: {
- files: {
- "assets/generated/babel.js": "assets/js/app.js",
- },
- },
- },
- copy: {
- packageLock: {
- src: "package-lock.json",
- dest: "dist/package-lock.json",
- },
- },
- });
-
- // Load the Grunt tasks.
- require("load-grunt-tasks")(grunt);
-
- // Register the tasks.
- grunt.registerTask("default", ["build"]);
- grunt.registerTask("build", [
- "copy:packageLock",
- "compass", // Compile CSS
- "htmlmin", // Minify HTML templates
- "filesToJavascript", // Convert HTML templates to JS variables
- "concat:variables", // Create finished variable.pass2.js file
- "rollup:main", // Rollup all the javascript files into one
- "concat:source", // Add banner to rollup result
- ]);
-
- grunt.registerTask("check", [
- // "flow", // Type-checking TODO: Fix flow
- "eslint", // Check for lint
- ]);
-
- grunt.registerTask("dist", [
- "build", // Build the script
- "check", // Check the script
- "uglify", // Minify the script
- ]);
-};
diff --git a/README.md b/README.md
index 869c7cd..65b7b3c 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,96 @@
-# Questionable Content Extensions User Script [![Build Status](https://travis-ci.org/Questionable-Content-Extensions/client.svg?branch=master)](https://travis-ci.org/Questionable-Content-Extensions/client) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.svg)](http://gruntjs.com/)
+# Questionable Content Extensions Userscript
+
+[![CI](https://github.com/Questionable-Content-Extensions/client/actions/workflows/CI.yml/badge.svg)](https://github.com/Questionable-Content-Extensions/client/actions/workflows/CI.yml)
+
+Questionable Content Extensions Userscript is the client-half of the Questionable Content Extensions stack. You can find the server-half [in this sibling repository](https://github.com/Questionable-Content-Extensions/client). The project also has [its own website](https://questionablextensions.net/).
+
+Questionable Content Extensions is a project to add additional features to the [Questionable Content](http://questionablecontent.net/) comic. The way it does this is by adding additional features to the webcomic. The extension has the following main features:
+
+It turns the site into a single-page application, so that navigating between comics doesn't require a full page reload. This makes reading the comic much more pleasant by requiring less bandwidth and thus being speedier/snappier.
+
+It adds additional navigation based on who's in the comics and where they're taking place.
## Getting Started
-To build this project, you can make use of a [Docker](https://www.docker.com/) container, which will take care of setting the build environment up for you (recommended) or, you can install the build environment on your own computer.
+### Using the usercript
+
+In Firefox or Chrome, install [Greasemonkey](https://addons.mozilla.org/en-CA/firefox/addon/greasemonkey/) or [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo).
+You can then install the userscript by installing by going [here](https://questionablextensions.net/releases/qc-ext.latest.user.js).
+
+### Building the userscript
+
+To build this project, you can install the build environment on your own computer, or you can make use of a [Docker](https://www.docker.com/) container, which will take care of setting the build environment up for you.
+
+#### Building on your own computer
+
+This project requires [Node.js®](https://nodejs.org/). Currently we're targeting Node v18.x, but it should most likely work with later versions too.
+
+Install it on your system in whatever manner is appropriate for your operating system, clone this repository, then run the following commands in the repository directory:
+
+```shell
+npm install
+npm build-userscript
+```
+
+This will produce two files in the `/dist` directory of this repository, `qc-ext.user.js`, which is a production build version of the user script and
+`qc-ext-dev.user.js`, which is a development build version of the user script. For doing development, you'll want to install the latter file in your userscript browser extension.
-### Building using Docker
+#### Developing on your own computer
+
+Because of the way this project is set up (based on ideas and code from [siefkenj/react-userscripts](https://github.com/siefkenj/react-userscripts/)), you don't have to re-build and re-install the script each time you make a change. Instead, build the script as described above and install `qc-ext-dev.user.js` in the browser once. Then, run the following command:
+
+```shell
+npm run start
+```
+
+Much like it works in a regular React project, this command will continuously rebuild the project as the code changes. In Chromium based browsers, the page will refresh automatically when this happens, in Firefox you must refresh manually.
+
+#### Building using Docker
+
+> **NOTE**: While testing, I've been having various issues using Docker for this purpose. Just be aware that while the instructions below should work in theory, you might run into the same issues I did, and may find it more productive to either develop directly on your own system, or make use of something like VSCode's dev-containers.
Install [Docker](https://www.docker.com/) if you don't already have it on your system. (If you're not familiar with Docker from before, reading the [Getting Started](https://www.docker.com/get-started) documentation is recommended to get a feel for what it is about.)
-Once Docker is installed, clone this repository, then run the build.ps1 file. If you're not using Powershell, run the commands from build.ps1 manually in your own shell.
+Once Docker is installed, clone this repository, then run the following command:
-After having run the commands, you should have files created at `dist\qc-ext.user.js`. This file can be opened in Greasemonkey or Tampermonkey directly.
+```shell
+docker build -t qcext-client .
+```
+
+You'll want to re-run this command if you change anything outside of `src`. To avoid having to re-run it when something inside `src` changes, we mount `src` as a volume inside the container.
-Whenever you've made changes and want to incorporate them into the user script, simply run the build script again.
+Once completed, you can run
-### Building on your own computer
+```shell
+docker run \
+ --mount "type=bind,source=${PWD}/src,target=/app/src" \
+ --mount "type=bind,source=${PWD}/dist,target=/app/dist" \
+ --rm -it \
+ qcext-client \
+ build-userscript
+```
-This project requires [Node.js®](https://nodejs.org/) and [Ruby](https://www.ruby-lang.org/). Install them on your system in whatever manner is appropriate for your operating system, clone this repository, then run the following commands in the repository directory:
+which will produce two files in the `/dist` directory of this repository, `qc-ext.user.js`, which is a production build version of the user script and
+`qc-ext-dev.user.js`, which is a development build version of the user script. For doing development, you'll want to install the latter file in your userscript browser extension.
+
+#### Developing using Docker
+
+Because of the way this project is set up (based on ideas and code from [siefkenj/react-userscripts](https://github.com/siefkenj/react-userscripts/)), you don't have to re-build and re-install the script each time you make a change. Instead, build the script as described above and install `qc-ext-dev.user.js` in the browser once. Then, run the following command:
```shell
-gem install compass # If you don't have compass installed already
-npm install -g grunt-cli # If you don't have grunt installed already
-npm install # To install all the grunt plugins we use
-grunt # To build our script
+docker run \
+ --mount "type=bind,source=${PWD}/src,target=/app/src" \
+ -p 8124:8124 \
+ --rm -it \
+ qcext-client \
+ start
```
-After having run the commands above, you should have files created at `dist\qc-ext.user.js`. This file can be opened in Greasemonkey or Tampermonkey directly.
+Much like it works in a regular React project, this command will continuously rebuild the project as the code changes. In Chromium based browsers, the page will refresh automatically when this happens, in Firefox you must refresh manually.
+
+If you're experiencing that it does not detect file changes, try uncommenting the commented out configuration near the end of `config-overrides.js` to see if it helps.
-Whenever you've made changes and want to incorporate them into the user script, simply run `grunt` again.
+
diff --git a/assets/js/app.js b/assets/js/app.js
deleted file mode 100644
index 7502945..0000000
--- a/assets/js/app.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import $ from "jquery";
-import angular from "angular";
-
-import "./modules/jQuery.changeElementType";
-
-import settings from "./modules/settings";
-import DomModifier from "./modules/dom-modifier";
-import { setup as setupAngular } from "./modules/angular-app";
-
-(async () => {
- await settings.loadSettings();
-
- const domModifier = new DomModifier();
- domModifier.modify();
-
- setupAngular();
-
- // Let's go!
- angular.bootstrap($("html").get(0), ["qc-spa"]);
-})();
diff --git a/assets/js/constants.js b/assets/js/constants.js
deleted file mode 100644
index ddccc66..0000000
--- a/assets/js/constants.js
+++ /dev/null
@@ -1,93 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-// Set this to true when working against your local test server.
-// NEVER CHECK THIS FILE IN WITH developmentMode = true!
-const developmentMode = false;
-
-function getSiteUrl() {
- return "https://questionablextensions.net/";
-}
-
-function getWebserviceBaseUrl() {
- if (developmentMode) {
- return "http://localhost:3000/api/";
- } else {
- return "https://questionablextensions.net/api/";
- }
-}
-
-const comicDataUrl = getWebserviceBaseUrl() + "comicdata/";
-const itemDataUrl = getWebserviceBaseUrl() + "itemdata/";
-const editLogUrl = getWebserviceBaseUrl() + "log";
-
-const constants = {
- settingsKey: "settings",
-
- developmentMode,
- siteUrl: getSiteUrl(),
- comicDataUrl,
- itemDataUrl,
- editLogUrl,
-
- // Comics after 3132 should have a tagline
- taglineThreshold: 3132,
-
- excludedComicsUrl: comicDataUrl + "excluded",
- addItemToComicUrl: comicDataUrl + "additem",
- removeItemFromComicUrl: comicDataUrl + "removeitem",
- setComicTitleUrl: comicDataUrl + "settitle",
- setComicTaglineUrl: comicDataUrl + "settagline",
- setPublishDateUrl: comicDataUrl + "setpublishdate",
- setGuestComicUrl: comicDataUrl + "setguest",
- setNonCanonUrl: comicDataUrl + "setnoncanon",
- setNoCastUrl: comicDataUrl + "setnocast",
- setNoLocationUrl: comicDataUrl + "setnolocation",
- setNoStorylineUrl: comicDataUrl + "setnostoryline",
- setNoTitleUrl: comicDataUrl + "setnotitle",
- setNoTaglineUrl: comicDataUrl + "setnotagline",
-
- itemImageUrl: itemDataUrl + "image/",
- itemFriendDataUrl: itemDataUrl + "friends/",
- itemLocationDataUrl: itemDataUrl + "locations/",
- setItemDataPropertyUrl: itemDataUrl + "setproperty",
-
- comicExtensions: ["png", "gif", "jpg"],
-
- comicdataLoadingEvent: "comicdata-loading",
- comicdataLoadedEvent: "comicdata-loaded",
- comicdataErrorEvent: "comicdata-error",
-
- itemdataLoadingEvent: "itemdata-loading",
- itemdataLoadedEvent: "itemdata-loaded",
- itemdataErrorEvent: "itemdata-error",
-
- itemsChangedEvent: "items-changed",
-
- maintenanceEvent: "maintenance",
-
- messages: {
- maintenance:
- "The Questionable Extensions" +
- " server is currently undergoing maintenance." +
- " Normal operation should resume within a" +
- " few minutes.",
- },
-};
-
-export default constants;
diff --git a/assets/js/modules/angular-app.js b/assets/js/modules/angular-app.js
deleted file mode 100644
index 111ec4f..0000000
--- a/assets/js/modules/angular-app.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import angular from "angular";
-
-import assemble from "./angular/assemble";
-
-const angularApp = angular.module("qc-spa", ["ui.router"]);
-export function setup() {
- assemble(angularApp);
-}
-
-export default angularApp;
diff --git a/assets/js/modules/angular/api/comicData.js b/assets/js/modules/angular/api/comicData.js
deleted file mode 100644
index df68a6d..0000000
--- a/assets/js/modules/angular/api/comicData.js
+++ /dev/null
@@ -1,81 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { ItemType } from "./itemData";
-
-export type ComicEditorDataMissing = {
- first: ?number,
- previous: ?number,
- next: ?number,
- last: ?number,
- any: boolean,
-};
-
-export type ComicEditorData = {
- missing: {
- cast: ComicEditorDataMissing,
- location: ComicEditorDataMissing,
- storyline: ComicEditorDataMissing,
- title: ComicEditorDataMissing,
- tagline: ComicEditorDataMissing,
- any: boolean,
- },
-};
-
-export type ComicItem = {
- first: ?number,
- previous: ?number,
- first: ?number,
- last: ?number,
- id: number,
- shortName: string,
- name: string,
- type: ItemType,
- color: string,
-};
-
-export type ComicData = {
- comic: number,
- imageType: "unknown" | "png" | "gif" | "jpeg",
- hasData: boolean,
- publishDate: ?string,
- isAccuratePublishDate: ?boolean,
- title: ?string,
- tagline: ?string,
- isGuestComic: ?boolean,
- isNonCanon: ?boolean,
- hasNoCast: ?boolean,
- hasNoLocation: ?boolean,
- hasNoStoryline: ?boolean,
- hasNoTitle: ?boolean,
- hasNoTagline: ?boolean,
- news: ?string,
- previous: ?number,
- next: ?number,
- editorData?: ComicEditorData,
- items: Array,
- allItems?: Array,
-};
-
-export type ComicItemRepository = {
- [string]: ComicItem[],
-
- cast?: ComicItem[],
- location?: ComicItem[],
- storyline?: ComicItem[],
-};
diff --git a/assets/js/modules/angular/api/itemData.js b/assets/js/modules/angular/api/itemData.js
deleted file mode 100644
index 827c12d..0000000
--- a/assets/js/modules/angular/api/itemData.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-export type ItemType = "cast" | "location" | "storyline";
-
-export type ItemBaseData = {
- id: number,
- shortName: string,
- name: string,
- type: ItemType,
-};
-
-export type ItemBaseDataWithColor = ItemBaseData & {
- color: string,
-};
-
-export type ItemData = ItemBaseDataWithColor & {
- first: number,
- last: number,
- appearances: number,
- totalComics: number,
- presence: number,
- hasImage: boolean,
-};
-
-export type ItemRelationData = ItemBaseDataWithColor & {
- count: number,
- percentage: number,
-};
-
-export type DecoratedItemData = ItemData & {
- highlightColor: string,
- locations: ItemRelationData[],
- friends: ItemRelationData[],
- imageUrls: string[],
-};
-
-export type ItemImageData = {
- id: number,
- crc32cHash: number,
-};
diff --git a/assets/js/modules/angular/api/logEntryData.js b/assets/js/modules/angular/api/logEntryData.js
deleted file mode 100644
index bc25371..0000000
--- a/assets/js/modules/angular/api/logEntryData.js
+++ /dev/null
@@ -1,30 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-export type LogEntry = {
- identifier: string,
- dateTime: string,
- action: string,
-};
-
-export type LogEntryData = {
- logEntries: LogEntry[],
- page: number,
- pageCount: number,
- logEntryCount: number,
-};
diff --git a/assets/js/modules/angular/assemble.js b/assets/js/modules/angular/assemble.js
deleted file mode 100644
index 12a65c3..0000000
--- a/assets/js/modules/angular/assemble.js
+++ /dev/null
@@ -1,93 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule } from "angular";
-
-import config from "./config";
-import run from "./run";
-
-import bodyController from "./controllers/bodyController";
-import comicController from "./controllers/comicController";
-import titleController from "./controllers/titleController";
-
-import colorService from "./services/colorService";
-import comicService from "./services/comicService";
-import itemService from "./services/itemService";
-import eventFactory from "./services/eventFactory";
-import eventService from "./services/eventService";
-import messageReportingService from "./services/messageReportingService";
-import styleService from "./services/styleService";
-
-import donutDirective from "./directives/donutDirective";
-import fileDataDirective from "./directives/fileDataDirective";
-import onErrorDirective from "./directives/onErrorDirective";
-import qcAddItemDirective from "./directives/qcAddItemDirective";
-import qcChangeLogDirective from "./directives/qcChangeLogDirective";
-import qcComicDirective from "./directives/qcComicDirective";
-import qcComicNavDirective from "./directives/qcComicNavDirective";
-import qcDateDirective from "./directives/qcDateDirective";
-import qcEditComicDataDirective from "./directives/qcEditComicDataDirective";
-import qcEditLogDirective from "./directives/qcEditLogDirective";
-import qcExtraDirective from "./directives/qcExtraDirective";
-import qcExtraNavDirective from "./directives/qcExtraNavDirective";
-import qcItemDetailsDirective from "./directives/qcItemDetailsDirective";
-import qcNavDirective from "./directives/qcNavDirective";
-import qcNewsDirective from "./directives/qcNewsDirective";
-import qcRibbonDirective from "./directives/qcRibbonDirective";
-import qcSetPublishDateDirective from "./directives/qcSetPublishDateDirective";
-import qcSetTaglineDirective from "./directives/qcSetTaglineDirective";
-import qcSettingsDirective from "./directives/qcSettingsDirective";
-import qcSetTitleDirective from "./directives/qcSetTitleDirective";
-
-export default function (app: AngularModule) {
- config(app);
- run(app);
-
- bodyController(app);
- comicController(app);
- titleController(app);
-
- colorService(app);
- comicService(app);
- itemService(app);
- eventFactory(app);
- eventService(app);
- messageReportingService(app);
- styleService(app);
-
- donutDirective(app);
- fileDataDirective(app);
- onErrorDirective(app);
- qcAddItemDirective(app);
- qcChangeLogDirective(app);
- qcComicDirective(app);
- qcComicNavDirective(app);
- qcDateDirective(app);
- qcEditComicDataDirective(app);
- qcEditLogDirective(app);
- qcExtraDirective(app);
- qcExtraNavDirective(app);
- qcItemDetailsDirective(app);
- qcNavDirective(app);
- qcNewsDirective(app);
- qcRibbonDirective(app);
- qcSetPublishDateDirective(app);
- qcSetTaglineDirective(app);
- qcSettingsDirective(app);
- qcSetTitleDirective(app);
-}
diff --git a/assets/js/modules/angular/config.js b/assets/js/modules/angular/config.js
deleted file mode 100644
index 2b4bce3..0000000
--- a/assets/js/modules/angular/config.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import GM from "greasemonkey";
-import angular from "angular";
-import type { AngularModule } from "angular";
-
-import settings from "./../settings";
-
-import decorateHttpService from "./decorateHttpService";
-import decorateScope from "./decorateScope";
-
-export default function (app: AngularModule) {
- // Set up routing and do other configuration
- app.config([
- "$stateProvider",
- "$urlRouterProvider",
- "$locationProvider",
- "$provide",
- "$logProvider",
- function (
- $stateProvider,
- $urlRouterProvider,
- $locationProvider,
- $provide,
- $logProvider
- ) {
- decorateHttpService($provide);
- decorateScope($provide);
-
- $stateProvider
- .state("homepage", {
- url: "^/$",
- controller: "comicController",
- controllerAs: "c",
- template: "",
- })
- .state("view", {
- url: "^/view.php?comic",
- controller: "comicController",
- controllerAs: "c",
- template: "",
- });
- $urlRouterProvider.otherwise(function ($injector, $location) {
- GM.openInTab($location.$$absUrl, false);
- });
-
- $locationProvider.html5Mode(true);
-
- $logProvider.debugEnabled(settings.values.showDebugLogs);
- },
- ]);
-}
diff --git a/assets/js/modules/angular/controllers/ControllerBases.js b/assets/js/modules/angular/controllers/ControllerBases.js
deleted file mode 100644
index d84daca..0000000
--- a/assets/js/modules/angular/controllers/ControllerBases.js
+++ /dev/null
@@ -1,129 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-import type { EventService } from "../services/eventService";
-import type { ComicData } from "../api/comicData";
-import type { ItemBaseData } from "../api/itemData";
-
-export class EventHandlingControllerBase {
- $scope: $DecoratedScope;
- eventService: EventService;
-
- constructor($scope: $DecoratedScope, eventService: EventService) {
- this.$scope = $scope;
- this.eventService = eventService;
-
- eventService.comicDataLoadingEvent.subscribe(
- $scope,
- (event, comic: number) => {
- $scope.safeApply(() => {
- this._comicDataLoading(comic);
- });
- }
- );
-
- eventService.comicDataLoadedEvent.subscribe($scope, (event, comicData) => {
- $scope.safeApply(() => {
- this._comicDataLoaded(comicData);
- });
- });
-
- eventService.comicDataErrorEvent.subscribe($scope, (event, error) => {
- $scope.safeApply(() => {
- this._comicDataError(error);
- });
- });
-
- eventService.itemsChangedEvent.subscribe($scope, (event, data) => {
- $scope.safeApply(() => {
- this._itemsChanged();
- });
- });
-
- eventService.itemDataLoadingEvent.subscribe($scope, (event, data) => {
- $scope.safeApply(() => {
- this._itemDataLoading();
- });
- });
-
- eventService.itemDataLoadedEvent.subscribe($scope, (event, itemData) => {
- $scope.safeApply(() => {
- this._itemDataLoaded(itemData);
- });
- });
-
- eventService.itemDataErrorEvent.subscribe($scope, (event, error) => {
- $scope.safeApply(() => {
- this._itemDataError(error);
- });
- });
-
- eventService.maintenanceEvent.subscribe($scope, (event, error) => {
- $scope.safeApply(() => {
- this._maintenance();
- });
- });
- }
-
- _comicDataLoading(comic: number) {}
-
- _comicDataLoaded(comicData: ComicData) {}
-
- _comicDataError(error: any) {}
-
- _itemsChanged() {}
-
- _itemDataLoading() {}
-
- _itemDataLoaded(itemData: ItemBaseData[]) {}
-
- _itemDataError(error: any) {}
-
- _maintenance() {}
-}
-
-export class SetValueControllerBase<
- TScope: Object
-> extends EventHandlingControllerBase {
- comicService: ComicService;
-
- unique: string;
-
- constructor(
- $scope: $DecoratedScope,
- comicService: ComicService,
- eventService: EventService
- ) {
- super($scope, eventService);
-
- this.comicService = comicService;
-
- this.unique = Math.random().toString(36).slice(-5);
- }
-
- _updateValue() {}
-
- keyPress(event: KeyboardEvent) {
- if (event.keyCode === 13) {
- // ENTER key
- this._updateValue();
- }
- }
-}
diff --git a/assets/js/modules/angular/controllers/bodyController.js b/assets/js/modules/angular/controllers/bodyController.js
deleted file mode 100644
index 4ef70ef..0000000
--- a/assets/js/modules/angular/controllers/bodyController.js
+++ /dev/null
@@ -1,116 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log, $Scope } from "angular";
-
-import settings from "../../settings";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-
-export default function (app: AngularModule) {
- app.controller("bodyController", [
- "$log",
- "$scope",
- "comicService",
- function (
- $log: $Log,
- $scope: $DecoratedScope,
- comicService: ComicService
- ) {
- $log.debug("START bodyController()");
-
- const isStupidFox =
- navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
-
- function previous() {
- $scope.$apply(function () {
- comicService.previous();
- });
- }
-
- function next() {
- $scope.$apply(function () {
- comicService.next();
- });
- }
-
- const shortcut = window.eval("window.shortcut");
-
- // Firefox balks at me trying to use the "shortcut" object from
- // my user script. Works just fine in Chrome. I can't be bothered
- // to cater to one browser's stupidity.
- if (isStupidFox) {
- const shortcutRemove = window
- .eval("window.shortcut.remove")
- .bind(shortcut);
- shortcutRemove("Left");
- shortcutRemove("Right");
-
- // This is a sort of replacement for "shortcut". Only supports
- // simple Left/Right navigation. Is missing the editor mode
- // shortcuts because Firefox is behaving like shit.
- window.addEventListener(
- "keydown",
- function (event) {
- // Only if no special keys are held down
- if (
- event.altKey ||
- event.ctrlKey ||
- event.metaKey ||
- event.shiftKey
- ) {
- return;
- }
-
- if (event.keyCode === 37) {
- // LEFT
- previous();
- } else if (event.keyCode === 39) {
- // RIGHT
- next();
- }
- },
- false
- );
- } else {
- // See how nice it can be done when your browser doesn't
- // actively try to sabotage you?
- shortcut.remove("Left");
- shortcut.remove("Right");
-
- shortcut.add("Left", previous, { disable_in_input: true });
- shortcut.add("Ctrl+Left", previous);
-
- shortcut.add("Right", next, { disable_in_input: true });
- shortcut.add("Ctrl+Right", next);
-
- shortcut.add(
- "Q",
- function () {
- if (settings.values.editMode) {
- $('input[id^="addItem"]').focus();
- }
- },
- { disable_in_input: true }
- );
- }
- $log.debug("END bodyController()");
- },
- ]);
-}
diff --git a/assets/js/modules/angular/controllers/comicController.js b/assets/js/modules/angular/controllers/comicController.js
deleted file mode 100644
index f273529..0000000
--- a/assets/js/modules/angular/controllers/comicController.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-import type { ComicService } from "../services/comicService";
-
-export default function (app: AngularModule) {
- app.controller("comicController", [
- "$log",
- "comicService",
- function ($log: $Log, comicService: ComicService) {
- $log.debug("START comicController()");
- this.comicService = comicService;
- $log.debug("END comicController()");
- },
- ]);
-}
diff --git a/assets/js/modules/angular/controllers/titleController.js b/assets/js/modules/angular/controllers/titleController.js
deleted file mode 100644
index 9117776..0000000
--- a/assets/js/modules/angular/controllers/titleController.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import constants from "../../../constants";
-
-import { EventHandlingControllerBase } from "./ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { EventService } from "../services/eventService";
-import type { ComicData } from "../api/comicData";
-
-export class TitleController extends EventHandlingControllerBase {
- static $inject: string[];
-
- $log: $Log;
-
- title: string;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- eventService: EventService
- ) {
- $log.debug("START TitleController");
-
- super($scope, eventService);
-
- this.$log = $log;
-
- this.title = "Loading Questionable Content Extension...";
-
- $log.debug("END TitleController");
- }
-
- _comicDataLoading(comic: number) {
- this.title = `Loading #${comic} — Questionable Content`;
- }
-
- _comicDataLoaded(comicData: ComicData) {
- if (comicData.hasData && comicData.title) {
- this.title = `#${comicData.comic}: ${comicData.title} — Questionable Content`;
- } else {
- this.title = `#${comicData.comic} — Questionable Content`;
- }
- }
-}
-TitleController.$inject = ["$scope", "$log", "eventService"];
-
-export default function (app: AngularModule) {
- app.controller("titleController", TitleController);
-}
diff --git a/assets/js/modules/angular/decorateHttpService.js b/assets/js/modules/angular/decorateHttpService.js
deleted file mode 100644
index 2bdc6df..0000000
--- a/assets/js/modules/angular/decorateHttpService.js
+++ /dev/null
@@ -1,244 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import GM from "greasemonkey";
-import angular from "angular";
-import $ from "jquery";
-
-// TODO: Since we're not using the original service at all, we might
-// as well completely replace it rather than decorate it...
-// http://www.bennadel.com/blog/
-// 2927-overriding-core-and-custom-services-in-angularjs.htm
-export default function ($provide: any) {
- // Let's take over $http and make it use Greasemonkey's cross-domain
- // XMLHTTPRequests instead of the browser's.
- $provide.decorator("$http", function () {
- // START Code bits borrowed from angular
- // (see angular's license for details)
- const APPLICATION_JSON = "application/json";
- const JSON_START = /^\[|^\{(?!\{)/;
- const JSON_ENDS = {
- "[": /]$/,
- "{": /}$/,
- };
- const JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/;
-
- const DEFAULT_HEADERS = {
- Accept: APPLICATION_JSON,
- "X-QCExt-Version": GM.info.script.version,
- };
-
- function isJsonLike(str) {
- const jsonStart = str.match(JSON_START);
-
- return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
- }
-
- function isString(value) {
- return typeof value === "string";
- }
-
- function fromJson(json) {
- return isString(json) ? JSON.parse(json) : json;
- }
-
- function defaultHttpResponseTransform(data, headers) {
- if (!isString(data)) {
- return data;
- }
-
- // Strip json vulnerability protection prefix
- // and trim whitespace
- const tempData = data.replace(JSON_PROTECTION_PREFIX, "").trim();
-
- if (!tempData) {
- return data;
- }
-
- const contentType = headers("Content-Type");
-
- if (
- (contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
- isJsonLike(tempData)
- ) {
- data = fromJson(tempData);
- }
-
- return data;
- }
-
- // END Code bits borrowed from angular
-
- function getHeaderFunction(headers) {
- const keyedHeaders = {};
-
- angular.forEach(headers, function (value) {
- const splitValue = value.trim().split(":", 2);
-
- if (splitValue.length < 2) {
- return;
- }
-
- keyedHeaders[splitValue[0].trim()] = splitValue[1].trim();
- });
-
- return function (key) {
- return keyedHeaders[key] || null;
- };
- }
-
- const injector = (angular: any).injector(["ng"]);
- const $q = injector.get("$q");
- const ourHttp = {
- get: function (url, config) {
- config = config || {};
-
- let headers: any = DEFAULT_HEADERS;
- if (config.headers) {
- headers = $.extend({}, DEFAULT_HEADERS, config.headers);
- }
- return $q(function (resolve, reject) {
- GM.xmlHttpRequest({
- method: "GET",
- url: url,
- headers: headers,
- onload: function (gmResponse) {
- const headers = getHeaderFunction(
- gmResponse.responseHeaders.split("\n")
- );
- let responseData = gmResponse.response;
-
- responseData = defaultHttpResponseTransform(
- responseData,
- headers
- );
- const response = {
- data: responseData,
- status: gmResponse.status,
- headers: headers,
- config: config,
- statusText: gmResponse.statusText,
- };
-
- resolve(response);
- },
- onerror: function (gmResponse) {
- const headers = getHeaderFunction(
- gmResponse.responseHeaders.split("\n")
- );
- let responseData = gmResponse.response;
-
- responseData = defaultHttpResponseTransform(
- responseData,
- headers
- );
- const response = {
- data: responseData,
- status: gmResponse.status,
- headers: headers,
- config: config,
- statusText: gmResponse.statusText,
- };
-
- reject(response);
- },
- });
- });
- },
- post: function (url, data, config) {
- config = config || {};
- const contentType =
- "contentType" in config ? config.contentType : APPLICATION_JSON;
- const dataTransform =
- "dataTransform" in config
- ? config.dataTransform
- : (d) => JSON.stringify(d);
-
- let headers: any = DEFAULT_HEADERS;
- if (config.headers) {
- headers = $.extend({}, DEFAULT_HEADERS, config.headers);
- }
- if (contentType) {
- headers["Content-Type"] = contentType;
- }
-
- return $q(function (resolve, reject) {
- GM.xmlHttpRequest({
- method: "POST",
- url: url,
- data: dataTransform(data),
- headers,
- onload: function (gmResponse) {
- const headers = getHeaderFunction(
- gmResponse.responseHeaders.split("\n")
- );
- let responseData = gmResponse.response;
-
- responseData = defaultHttpResponseTransform(
- responseData,
- headers
- );
- const response = {
- data: responseData,
- status: gmResponse.status,
- headers: headers,
- config: config,
- statusText: gmResponse.statusText,
- };
-
- resolve(response);
- },
- onerror: function (gmResponse) {
- const headers = getHeaderFunction(
- gmResponse.responseHeaders.split("\n")
- );
- let responseData = gmResponse.response;
-
- responseData = defaultHttpResponseTransform(
- responseData,
- headers
- );
- const response = {
- data: responseData,
- status: gmResponse.status,
- headers: headers,
- config: config,
- statusText: gmResponse.statusText,
- };
-
- reject(response);
- },
- });
- });
- },
- };
-
- /* Methods/properties to implement for full compatibility:
- * pendingRequests
- * delete
- * head
- * jsonp
- * post
- * put
- * patch
- * defaults
- */
-
- return ourHttp;
- });
-}
diff --git a/assets/js/modules/angular/decorateScope.js b/assets/js/modules/angular/decorateScope.js
deleted file mode 100644
index 6f26f62..0000000
--- a/assets/js/modules/angular/decorateScope.js
+++ /dev/null
@@ -1,46 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import GM from "greasemonkey";
-
-import type { $Scope } from "angular";
-
-export type $DecoratedScope = $Scope & {
- safeApply(fn: Function): void,
-};
-
-export default function ($provide: any) {
- $provide.decorator("$rootScope", [
- "$delegate",
- function ($delegate) {
- const $rootScopePrototype = Object.getPrototypeOf($delegate);
- $rootScopePrototype.safeApply = function (fn) {
- const phase = this.$root.$$phase;
- if (phase === "$apply" || phase === "$digest") {
- if (fn && typeof fn === "function") {
- fn();
- }
- } else {
- this.$apply(fn);
- }
- };
-
- return $delegate;
- },
- ]);
-}
diff --git a/assets/js/modules/angular/directives/donutDirective.js b/assets/js/modules/angular/directives/donutDirective.js
deleted file mode 100644
index fcc9ff0..0000000
--- a/assets/js/modules/angular/directives/donutDirective.js
+++ /dev/null
@@ -1,69 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule } from "angular";
-
-import variables from "../../../../generated/variables.pass2";
-
-export default function (app: AngularModule) {
- app.directive("donut", function () {
- return {
- restrict: "E",
- scope: {
- size: "@",
- innerColor: "@",
- color: "@",
- highlightColor: "@",
- percent: "@",
- borderSize: "@",
- },
- controller: [
- "$scope",
- function ($scope) {
- function calculateRotationValues() {
- $scope.rotation = ($scope.percent / 100) * 180;
- $scope.fixRotation = $scope.rotation * 2;
- }
- function calculateSizeValues() {
- $scope.maskClip = {
- top: 0,
- right: $scope.size,
- bottom: $scope.size,
- left: $scope.size / 2,
- };
- $scope.fillClip = {
- top: 0,
- right: $scope.size / 2,
- bottom: $scope.size,
- left: 0,
- };
- $scope.insetSize = $scope.size - $scope.borderSize;
- $scope.insetMargin = $scope.borderSize / 2;
- }
- calculateRotationValues();
- calculateSizeValues();
-
- $scope.$watch("percent", calculateRotationValues);
- $scope.$watch("size", calculateSizeValues);
- $scope.$watch("borderSize", calculateSizeValues);
- },
- ],
- template: variables.html.donut,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/fileDataDirective.js b/assets/js/modules/angular/directives/fileDataDirective.js
deleted file mode 100644
index ae6db1c..0000000
--- a/assets/js/modules/angular/directives/fileDataDirective.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule } from "angular";
-
-export default function (app: AngularModule) {
- app.directive("fileData", function () {
- return {
- restrict: "A",
- scope: {
- fileData: "=",
- fileInfo: "=",
- },
- link: function (scope, element, attrs) {
- element.bind("change", function (changeEvent) {
- const fileReader = new FileReader();
- fileReader.onload = function (loadEvent) {
- scope.$apply(function () {
- scope.fileInfo = changeEvent.target.files[0];
- scope.fileData = loadEvent.target.result;
- });
- };
- fileReader.readAsDataURL(changeEvent.target.files[0]);
- });
- },
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/onErrorDirective.js b/assets/js/modules/angular/directives/onErrorDirective.js
deleted file mode 100644
index ee13b85..0000000
--- a/assets/js/modules/angular/directives/onErrorDirective.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule } from "angular";
-
-export default function (app: AngularModule) {
- app.directive("onError", function () {
- return {
- restrict: "A",
- link: function (scope, element, attrs) {
- element.bind("error", function () {
- scope.$apply(attrs.onError);
- });
- },
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcAddItemDirective.js b/assets/js/modules/angular/directives/qcAddItemDirective.js
deleted file mode 100644
index 717a8a6..0000000
--- a/assets/js/modules/angular/directives/qcAddItemDirective.js
+++ /dev/null
@@ -1,291 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log, $Timeout, $Filter } from "angular";
-
-import constants from "../../../constants";
-import variables from "../../../../generated/variables.pass2";
-
-import { SetValueControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-import type { ItemService } from "../services/itemService";
-import type { EventService } from "../services/eventService";
-import type { MessageReportingService } from "../services/messageReportingService";
-import type { ItemBaseData, ItemData } from "../api/itemData";
-import type { ComicData, ComicItem } from "../api/comicData";
-
-const newItemId = -1;
-const maintenanceId = -2;
-const errorId = -3;
-
-const addCastTemplate = "Add new cast member";
-const addCastItem: ItemBaseData = {
- id: newItemId,
- type: "cast",
- shortName: `${addCastTemplate} ''`,
- name: "",
-};
-const addStorylineTemplate = "Add new storyline";
-const addStorylineItem: ItemBaseData = {
- id: newItemId,
- type: "storyline",
- shortName: `${addStorylineTemplate} ''`,
- name: "",
-};
-const addLocationTemplate = "Add new location";
-const addLocationItem: ItemBaseData = {
- id: newItemId,
- type: "location",
- shortName: `${addLocationTemplate} ''`,
- name: "",
-};
-const maintenanceItem: ItemBaseData = {
- id: maintenanceId,
- type: "cast",
- shortName: "Maintenance ongoing. Choose this to attempt refresh.",
- name: "",
-};
-const errorItem: ItemBaseData = {
- id: errorId,
- type: "cast",
- shortName: "Error loading item list. Choose this to attempt refresh.",
- name: "",
-};
-
-function escapeRegExp(s) {
- return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
-}
-
-let triggeredFocus = false;
-let dropdownOpen = false;
-let firstRun = true;
-
-export class AddItemController extends SetValueControllerBase {
- static $inject: string[];
-
- $log: $Log;
- $timeout: $Timeout;
- $filter: $Filter;
-
- itemService: ItemService;
- messageReportingService: MessageReportingService;
-
- searchFieldId: string;
- dropdownId: string;
- dropdownButtonId: string;
-
- itemFilterText: string;
- items: ItemBaseData[];
-
- isUpdating: boolean;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- comicService: ComicService,
- eventService: EventService,
- itemService: ItemService,
- $timeout: $Timeout,
- $filter: $Filter,
- messageReportingService: MessageReportingService
- ) {
- $log.debug("START AddItemController");
-
- super($scope, comicService, eventService);
-
- this.$log = $log;
- this.$timeout = $timeout;
- this.$filter = $filter;
-
- this.itemService = itemService;
- this.messageReportingService = messageReportingService;
-
- this.searchFieldId = `#addItem_${this.unique}_search`;
- this.dropdownId = `#addItem_${this.unique}_dropdown`;
- this.dropdownButtonId = `#addItem_${this.unique}_dropdownButton`;
-
- this.items = [];
- this.itemFilterText = "";
-
- (this: any).itemFilter = this.itemFilter.bind(this);
-
- $log.debug("END AddItemController");
- }
-
- _itemDataLoaded(itemData: ItemBaseData[]) {
- itemData = itemData.slice(0);
-
- itemData.push(addCastItem);
- itemData.push(addStorylineItem);
- itemData.push(addLocationItem);
-
- this.$scope.safeApply(() => {
- this.items = itemData;
- });
- }
-
- _itemDataError(error: any) {
- this.items.length = 0;
- this.items.push(errorItem);
- }
-
- _maintenance() {
- this.items.length = 0;
- this.items.push(maintenanceItem);
- }
-
- _updateValue() {
- // Add the top item in the list
- const filteredList = this.$filter("filter")(this.items, this.itemFilter);
- const chosenItem = filteredList[0];
- this.addItem(chosenItem);
- }
-
- _comicDataLoaded(comicData: ComicData) {
- this.itemFilterText = "";
- this.$scope.isUpdating = false;
- }
-
- searchChanged() {
- let filterText = this.itemFilterText;
-
- if (filterText.charAt(0) === "!") {
- filterText = filterText.substr(1);
- } else if (filterText.charAt(0) === "@") {
- filterText = filterText.substr(1);
- } else if (filterText.charAt(0) === "#") {
- filterText = filterText.substr(1);
- }
-
- addCastItem.shortName = `${addCastTemplate} '${filterText}'`;
- addCastItem.name = filterText;
- addStorylineItem.shortName = `${addStorylineTemplate} '${filterText}'`;
- addStorylineItem.name = filterText;
- addLocationItem.shortName = `${addLocationTemplate} '${filterText}'`;
- addLocationItem.name = filterText;
- }
-
- itemFilter(value: ItemBaseData) {
- let filterText = this.itemFilterText;
-
- let result = true;
- if (filterText.charAt(0) === "!") {
- result = value.type === "cast";
- filterText = filterText.substr(1);
- } else if (filterText.charAt(0) === "@") {
- result = value.type === "location";
- filterText = filterText.substr(1);
- } else if (filterText.charAt(0) === "#") {
- result = value.type === "storyline";
- filterText = filterText.substr(1);
- }
-
- const searchRegex = new RegExp(escapeRegExp(filterText), "i");
- result = result && value.shortName.match(searchRegex) !== null;
-
- return result;
- }
-
- focusSearch() {
- this.$log.debug("AddItemController::focusSearch(): #1 Search focused");
- if (firstRun) {
- $(this.dropdownId).on("shown.bs.dropdown", () => {
- dropdownOpen = true;
- this.$log.debug("AddItemController::focusSearch(): #4 Dropdown opened");
-
- // Opening the dropdown makes the search field
- // lose focus. So set it again.
- $(this.searchFieldId).focus();
- triggeredFocus = false;
-
- $(this.dropdownId + " .dropdown-menu").width(
- $(this.dropdownId).width()
- );
- });
- $(this.dropdownId).on("hidden.bs.dropdown", () => {
- this.$log.debug("AddItemController::focusSearch(): #5 Dropdown closed");
- dropdownOpen = false;
- });
-
- firstRun = false;
- }
-
- if (!dropdownOpen && !triggeredFocus) {
- this.$log.debug(
- "AddItemController::focusSearch(): #2 Focus was user-initiated"
- );
- triggeredFocus = true;
- this.$timeout(() => {
- if (!dropdownOpen) {
- this.$log.debug(
- "AddItemController::focusSearch(): #3 Toggle dropdown"
- );
- ($(this.dropdownButtonId): any).dropdown("toggle");
- }
- }, 150);
- }
- }
-
- async addItem(item: ComicItem) {
- if (item.id == maintenanceId || item.id == errorId) {
- this.itemService.refreshItemData();
- return;
- }
-
- this.$scope.isUpdating = true;
- const response = await this.comicService.addItemToComic(item);
- if (response.status === 200) {
- // TODO: Maybe move this new item event logic to comicService.addItemToComic?
- if (item.id == newItemId) {
- this.eventService.itemsChangedEvent.publish();
- }
- this.$scope.safeApply(() => {
- this.itemFilterText = "";
- });
- } else {
- this.$scope.safeApply(() => {
- this.$scope.isUpdating = false;
- });
- }
- }
-}
-AddItemController.$inject = [
- "$scope",
- "$log",
- "comicService",
- "eventService",
- "itemService",
- "$timeout",
- "$filter",
- "messageReportingService",
-];
-
-export default function (app: AngularModule) {
- app.directive("qcAddItem", function () {
- return {
- restrict: "E",
- replace: true,
- scope: { isUpdating: "=" },
- controller: AddItemController,
- controllerAs: "a",
- template: variables.html.addItem,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcChangeLogDirective.js b/assets/js/modules/angular/directives/qcChangeLogDirective.js
deleted file mode 100644
index 87294da..0000000
--- a/assets/js/modules/angular/directives/qcChangeLogDirective.js
+++ /dev/null
@@ -1,103 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import GM from "greasemonkey";
-
-import constants from "../../../constants";
-import settings from "../../settings";
-import variables from "../../../../generated/variables.pass2";
-
-import { EventHandlingControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { EventService } from "../services/eventService";
-import type { ComicData } from "../api/comicData";
-
-export class ChangeLogController extends EventHandlingControllerBase {
- static $inject: string[];
-
- $log: $Log;
-
- versionUpdated: boolean;
- currentVersion: string;
- previousVersion: ?string;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- eventService: EventService
- ) {
- $log.debug("START ChangeLogController");
-
- super($scope, eventService);
-
- this.$log = $log;
-
- this.versionUpdated = false;
- this.currentVersion = GM.info.script.version;
- this.previousVersion = null;
-
- $("#changeLogDialog").on("hide.bs.modal", async () => {
- this.$log.debug("Saving settings...");
- settings.values.version = this.currentVersion;
- await settings.saveSettings();
- this.$log.debug("Settings saved.");
- });
-
- $log.debug("END ChangeLogController");
- }
-
- _comicDataLoaded(comicData: ComicData) {
- if (!settings.values.version) {
- // Version is undefined. We're a new user!
- this.$log.debug(
- "ChangeLogController::_comicDataLoaded(): Version undefined!"
- );
- } else if (settings.values.version !== this.currentVersion) {
- // Version is changed. Script has been updated!
- // Show the change log dialog.
- this.previousVersion = settings.values.version;
- this.$log.debug(
- "ChangeLogController::_comicDataLoaded(): Version different!"
- );
- } else {
- return;
- }
- this.versionUpdated = true;
- }
-
- close() {
- ($("#changeLogDialog"): any).modal("hide");
- }
-}
-ChangeLogController.$inject = ["$scope", "$log", "eventService"];
-
-export default function (app: AngularModule) {
- app.directive("qcChangeLog", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: ChangeLogController,
- controllerAs: "clvm",
- template: variables.html.changeLog,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcComicDirective.js b/assets/js/modules/angular/directives/qcComicDirective.js
deleted file mode 100644
index 47b5ee0..0000000
--- a/assets/js/modules/angular/directives/qcComicDirective.js
+++ /dev/null
@@ -1,223 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-import variables from "../../../../generated/variables.pass2";
-
-import constants from "../../../constants";
-import settings from "../../settings";
-
-import { EventHandlingControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { EventService } from "../services/eventService";
-import type { ComicService } from "../services/comicService";
-import type { MessageReportingService } from "../services/messageReportingService";
-
-import type { ComicData } from "../api/comicData";
-
-// TODO: once the directive has been set up and the image loaded, remove the original.
-export class ComicController extends EventHandlingControllerBase {
- static $inject: string[];
-
- $scope: $DecoratedScope;
- $log: $Log;
-
- comicService: ComicService;
- messageReportingService: MessageReportingService;
-
- isInitializing: boolean;
- isLoading: boolean;
- comicImage: string;
-
- comicDataCache: { [number]: string };
-
- originalComicAnchor: ?JQuery;
- imageLoadingTimeout: ?any;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- eventService: EventService,
- comicService: ComicService,
- messageReportingService: MessageReportingService
- ) {
- $log.debug("START ComicController");
-
- super($scope, eventService);
- this.$log = $log;
-
- this.comicService = comicService;
- this.messageReportingService = messageReportingService;
-
- this.isInitializing = true;
- this.isLoading = true;
- this.comicDataCache = {};
-
- const comicImg = $('img[src*="/comics/"]');
- this.originalComicAnchor = comicImg.parent("a");
-
- $log.debug("END ComicController");
- }
-
- _comicImageLoaded(src: ?string) {
- if (this.imageLoadingTimeout) {
- window.clearTimeout(this.imageLoadingTimeout);
- this.imageLoadingTimeout = null;
- }
-
- this.$scope.safeApply(() => {
- if (src) {
- this.comicImage = src;
- }
- this.isLoading = false;
-
- if (this.isInitializing) {
- if (this.originalComicAnchor) {
- this.originalComicAnchor.remove();
- this.originalComicAnchor = null;
- }
- this.isInitializing = false;
- }
- });
- }
-
- _comicImageError(comic: number) {
- this.messageReportingService.reportError(
- "Could not load image for comic " + comic
- );
- this._comicImageLoaded(null);
- }
-
- _extensionImageLoading(comic: number, imageExtension: string) {
- const downloadingImage = new Image();
- downloadingImage.onload = () => {
- this.comicDataCache[comic] = imageExtension;
- this._comicImageLoaded(downloadingImage.src);
- };
- downloadingImage.onerror = () => {
- this._comicImageError(comic);
- };
- const imageUrl = `${window.location.origin}/comics/${comic}.${imageExtension}`;
- downloadingImage.src = imageUrl;
- }
-
- _fallbackImageLoading(comic: number) {
- let comicExtensionIndex = 0;
-
- const downloadingImage = new Image();
- downloadingImage.onload = () => {
- this.$log.debug(
- "ComicController::_fallbackImageLoading() -- Image loaded"
- );
- this.comicDataCache[comic] =
- constants.comicExtensions[comicExtensionIndex];
- this._comicImageLoaded(downloadingImage.src);
- };
- downloadingImage.onerror = () => {
- this.$log.debug(
- "ComicController::_fallbackImageLoading() -- Image failed to load"
- );
- if (comicExtensionIndex < constants.comicExtensions.length - 1) {
- comicExtensionIndex++;
- this.$log.debug(
- "ComicController::_fallbackImageLoading() -- Trying " +
- constants.comicExtensions[comicExtensionIndex]
- );
- const imageUrl = `${window.location.origin}/comics/${comic}.${constants.comicExtensions[comicExtensionIndex]}`;
- downloadingImage.src = imageUrl;
- } else {
- this._comicImageError(comic);
- }
- };
-
- this.$log.debug(
- "ComicController::_fallbackImageLoading() -- Trying " +
- constants.comicExtensions[comicExtensionIndex]
- );
- const imageUrl = `${window.location.origin}/comics/${comic}.${constants.comicExtensions[comicExtensionIndex]}`;
- downloadingImage.src = imageUrl;
- }
-
- _comicDataLoading(comic: number) {
- let comicLoadingIndicatorDelay = settings.values.comicLoadingIndicatorDelay;
- if (comicLoadingIndicatorDelay < 0) {
- comicLoadingIndicatorDelay = 0;
- }
- if (this.imageLoadingTimeout) {
- window.clearTimeout(this.imageLoadingTimeout);
- this.imageLoadingTimeout = null;
- }
- this.imageLoadingTimeout = window.setTimeout(() => {
- this.$log.debug(
- "ComicController::_comicDataLoading - imageLoadingTimeout triggered"
- );
- this.imageLoadingTimeout = null;
- this.$scope.safeApply(() => {
- this.isLoading = true;
- });
- }, comicLoadingIndicatorDelay);
-
- if (comic in this.comicDataCache) {
- this._extensionImageLoading(comic, this.comicDataCache[comic]);
- }
- }
-
- _comicDataLoaded(comicData: ComicData) {
- if (comicData.comic in this.comicDataCache) {
- return;
- }
-
- if (!comicData.hasData) {
- this._fallbackImageLoading(this.comicService.comic);
- return;
- }
-
- if (comicData.imageType == "unknown") {
- this._fallbackImageLoading(comicData.comic);
- return;
- }
-
- let imageExtension = comicData.imageType;
- if (imageExtension == "jpeg") imageExtension = "jpg";
- this._extensionImageLoading(comicData.comic, imageExtension);
- }
-
- _comicDataError(error: any) {
- this._fallbackImageLoading(this.comicService.comic);
- }
-}
-ComicController.$inject = [
- "$scope",
- "$log",
- "eventService",
- "comicService",
- "messageReportingService",
-];
-
-export default function (app: AngularModule) {
- app.directive("qcComic", function () {
- return {
- restrict: "E",
- scope: {},
- controller: ComicController,
- controllerAs: "c",
- template: variables.html.comic,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcComicNavDirective.js b/assets/js/modules/angular/directives/qcComicNavDirective.js
deleted file mode 100644
index e9b0ef0..0000000
--- a/assets/js/modules/angular/directives/qcComicNavDirective.js
+++ /dev/null
@@ -1,106 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import constants from "../../../constants";
-import variables from "../../../../generated/variables.pass2";
-
-import { EventHandlingControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-import type { EventService } from "../services/eventService";
-import type { ComicData } from "../api/comicData";
-
-export class ComicNavController extends EventHandlingControllerBase {
- static $inject: string[];
-
- $log: $Log;
- comicService: ComicService;
- latestComic: number;
-
- currentComic: ?number;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- comicService: ComicService,
- eventService: EventService,
- latestComic: number
- ) {
- $log.debug("START ComicNavController");
-
- super($scope, eventService);
-
- this.$log = $log;
- this.comicService = comicService;
- this.latestComic = latestComic;
-
- this.currentComic = null;
-
- $log.debug("END ComicNavController");
- }
-
- _comicDataLoaded(comicData: ComicData) {
- this.currentComic = comicData.comic;
- }
-
- go() {
- this.$log.debug(
- `ComicNavController::go(): ${
- this.currentComic ? this.currentComic : "NONE"
- }`
- );
- if (!this.currentComic) {
- this.currentComic = this.latestComic;
- } else if (this.currentComic < 1) {
- this.currentComic = 1;
- } else if (this.currentComic > this.latestComic) {
- this.currentComic = this.latestComic;
- }
- this.comicService.gotoComic(this.currentComic);
- }
-
- keyPress(event: KeyboardEvent) {
- if (event.keyCode === 13) {
- // ENTER key
- this.go();
- }
- }
-}
-ComicNavController.$inject = [
- "$scope",
- "$log",
- "comicService",
- "eventService",
- "latestComic",
-];
-
-export default function (app: AngularModule) {
- app.directive("qcComicNav", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: ComicNavController,
- controllerAs: "cn",
- template: variables.html.comicNav,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcDateDirective.js b/assets/js/modules/angular/directives/qcDateDirective.js
deleted file mode 100644
index a0ed2a1..0000000
--- a/assets/js/modules/angular/directives/qcDateDirective.js
+++ /dev/null
@@ -1,89 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import constants from "../../../constants";
-import settings, { Settings } from "../../settings";
-import variables from "../../../../generated/variables.pass2";
-
-import { EventHandlingControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { EventService } from "../services/eventService";
-import type { ComicData } from "../api/comicData";
-
-export class DateController extends EventHandlingControllerBase {
- static $inject: string[];
-
- $log: $Log;
-
- settings: Settings;
-
- date: ?Date;
- approximateDate: boolean;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- eventService: EventService
- ) {
- $log.debug("START DateController");
-
- super($scope, eventService);
-
- this.$log = $log;
-
- this.settings = settings;
- this.date = null;
- this.approximateDate = false;
-
- $log.debug("END DateController");
- }
-
- _comicDataLoading(comic: number) {
- self.date = null;
- self.approximateDate = false;
- }
-
- _comicDataLoaded(comicData: ComicData) {
- this.approximateDate = !comicData.isAccuratePublishDate;
- const publishDate = comicData.publishDate;
- this.$log.debug("DateController::_comicDataLoaded(): ", publishDate);
- if (publishDate !== null && publishDate !== undefined) {
- const date = new Date(publishDate);
- this.date = date;
- } else {
- this.date = null;
- }
- }
-}
-DateController.$inject = ["$scope", "$log", "eventService"];
-
-export default function (app: AngularModule) {
- app.directive("qcDate", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: DateController,
- controllerAs: "d",
- template: variables.html.date,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcEditComicDataDirective.js b/assets/js/modules/angular/directives/qcEditComicDataDirective.js
deleted file mode 100644
index dacc202..0000000
--- a/assets/js/modules/angular/directives/qcEditComicDataDirective.js
+++ /dev/null
@@ -1,184 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import angular from "angular";
-
-import constants from "../../../constants";
-import variables from "../../../../generated/variables.pass2";
-
-import { EventHandlingControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-import type { EventService } from "../services/eventService";
-import type { ComicData, ComicItem } from "../api/comicData";
-import type { ItemType } from "../api/itemData";
-
-export class EditComicDataController extends EventHandlingControllerBase {
- static $inject: string[];
-
- $log: $Log;
- comicService: ComicService;
-
- isUpdating: boolean;
-
- editData: any; // TODO: Make properly strongly typed
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- eventService: EventService,
- comicService: ComicService
- ) {
- $log.debug("START EditComicDataController");
-
- super($scope, eventService);
-
- this.$log = $log;
- this.comicService = comicService;
-
- this.isUpdating = true;
-
- $("#editComicDataDialog").on("show.bs.modal", () => {
- // If something needs to be done, do it here.
- });
-
- $log.debug("END EditComicDataController");
- }
-
- _comicDataLoading(comic: number) {
- this.isUpdating = true;
- }
-
- _comicDataLoaded(comicData: ComicData) {
- const editData: {
- comicData: ComicData,
- [ItemType]: { [number]: ComicItem },
- } = { comicData: comicData };
-
- if (comicData.hasData) {
- angular.forEach(comicData.items, (item) => {
- let editDataType: { [number]: ComicItem };
- if (!editData[item.type]) {
- editDataType = editData[item.type] = {};
- } else {
- editDataType = editData[item.type];
- }
-
- editDataType[item.id] = item;
- });
- }
-
- this.editData = editData;
- this.isUpdating = false;
- }
-
- _handleUpdateResponse(response: any, resetValueKey: ?string) {
- if (response.status !== 200) {
- this.$scope.safeApply(() => {
- if (resetValueKey) {
- this.editData.comicData[resetValueKey] =
- !this.editData.comicData[resetValueKey];
- }
- this.isUpdating = false;
- });
- }
- }
-
- async remove(item: ComicItem) {
- this.$scope.safeApply(() => {
- this.isUpdating = true;
- });
- const response = await this.comicService.removeItem(item);
- this._handleUpdateResponse(response);
- }
-
- changeGuestComic() {
- this.isUpdating = true;
- this.comicService
- .setGuestComic(this.editData.comicData.isGuestComic)
- .then((r) => this._handleUpdateResponse(r, "isGuestComic"));
- }
-
- changeNonCanon() {
- this.isUpdating = true;
- this.comicService
- .setNonCanon(this.editData.comicData.isNonCanon)
- .then((r) => this._handleUpdateResponse(r, "isNonCanon"));
- }
-
- changeNoCast() {
- this.isUpdating = true;
- this.comicService
- .setNoCast(this.editData.comicData.hasNoCast)
- .then((r) => this._handleUpdateResponse(r, "hasNoCast"));
- }
-
- changeNoLocation() {
- this.isUpdating = true;
- this.comicService
- .setNoLocation(this.editData.comicData.hasNoLocation)
- .then((r) => this._handleUpdateResponse(r, "hasNoLocation"));
- }
-
- changeNoStoryline() {
- this.isUpdating = true;
- this.comicService
- .setNoStoryline(this.editData.comicData.hasNoStoryline)
- .then((r) => this._handleUpdateResponse(r, "hasNoStoryline"));
- }
-
- changeNoTitle() {
- this.isUpdating = true;
- this.comicService
- .setNoTitle(this.editData.comicData.hasNoTitle)
- .then((r) => this._handleUpdateResponse(r, "hasNoTitle"));
- }
-
- changeNoTagline() {
- this.isUpdating = true;
- this.comicService
- .setNoTagline(this.editData.comicData.hasNoTagline)
- .then((r) => this._handleUpdateResponse(r, "hasNoTagline"));
- }
-
- close() {
- ($("#editComicDataDialog"): any).modal("hide");
- }
-}
-EditComicDataController.$inject = [
- "$scope",
- "$log",
- "eventService",
- "comicService",
-];
-
-export default function (app: AngularModule) {
- app.directive("qcEditComicData", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: EditComicDataController,
- controllerAs: "ecdvm",
- template: variables.html.editComicData,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcEditLogDirective.js b/assets/js/modules/angular/directives/qcEditLogDirective.js
deleted file mode 100644
index d5e89b8..0000000
--- a/assets/js/modules/angular/directives/qcEditLogDirective.js
+++ /dev/null
@@ -1,140 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log, $Http } from "angular";
-
-import angular from "angular";
-
-import constants from "../../../constants";
-import settings from "../../settings";
-import variables from "../../../../generated/variables.pass2";
-
-import { EventHandlingControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-import type { EventService } from "../services/eventService";
-import type { MessageReportingService } from "../services/messageReportingService";
-import type { ComicData, ComicItem } from "../api/comicData";
-import type { LogEntryData } from "../api/logEntryData";
-
-export class EditLogController extends EventHandlingControllerBase {
- static $inject: string[];
-
- $scope: $DecoratedScope;
- $log: $Log;
- $http: $Http;
- messageReportingService: MessageReportingService;
-
- isLoading: boolean;
-
- currentPage: number;
- logEntryData: LogEntryData;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- $http: $Http,
- messageReportingService: MessageReportingService,
- eventService: EventService
- ) {
- $log.debug("START EditLogController");
-
- super($scope, eventService);
-
- this.$log = $log;
- this.$http = $http;
- this.messageReportingService = messageReportingService;
-
- this.currentPage = 1;
-
- $("#editLogDialog").on("show.bs.modal", () => {
- this.currentPage = 1;
- this._loadLogs();
- });
-
- $log.debug("END EditLogController");
- }
-
- _maintenance() {
- this.close();
- }
-
- async _loadLogs() {
- this.$scope.safeApply(() => {
- this.isLoading = true;
- });
- const response = await this.$http.get(
- `${constants.editLogUrl}?page=${this.currentPage}&token=${settings.values.editModeToken}`
- );
- this.$scope.safeApply(() => {
- this.isLoading = false;
- });
- if (response.status === 200) {
- this.$scope.safeApply(() => {
- this.logEntryData = (response.data: LogEntryData);
- });
- } else {
- if (response.status === 503) {
- this.eventService.maintenanceEvent.publish();
- } else {
- this.messageReportingService.reportError(response.data);
- }
- }
- }
-
- previousPage() {
- this.currentPage--;
- if (this.currentPage < 1) {
- this.currentPage = 1;
- }
- this._loadLogs();
- }
-
- nextPage() {
- this.currentPage++;
- if (this.currentPage > this.logEntryData.pageCount) {
- this.currentPage = this.logEntryData.pageCount;
- }
- this._loadLogs();
- }
-
- close() {
- ($("#editLogDialog"): any).modal("hide");
- }
-}
-EditLogController.$inject = [
- "$scope",
- "$log",
- "$http",
- "messageReportingService",
- "eventService",
-];
-
-export default function (app: AngularModule) {
- app.directive("qcEditLog", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: EditLogController,
- controllerAs: "elvm",
- template: variables.html.editLog,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcExtraDirective.js b/assets/js/modules/angular/directives/qcExtraDirective.js
deleted file mode 100644
index 30aaebc..0000000
--- a/assets/js/modules/angular/directives/qcExtraDirective.js
+++ /dev/null
@@ -1,331 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log, $Sce } from "angular";
-
-import GM from "greasemonkey";
-import angular from "angular";
-
-import constants from "../../../constants";
-import settings, { Settings } from "../../settings";
-import variables from "../../../../generated/variables.pass2";
-
-import { EventHandlingControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ColorService } from "../services/colorService";
-import type { ComicService } from "../services/comicService";
-import type { EventService } from "../services/eventService";
-import type { MessageReportingService } from "../services/messageReportingService";
-import type { StyleService } from "../services/styleService";
-import type { ItemData } from "../api/itemData";
-import type {
- ComicItemRepository,
- ComicData,
- ComicEditorData,
- ComicItem,
-} from "../api/comicData";
-
-export class ExtraController extends EventHandlingControllerBase {
- static $inject: string[];
-
- $log: $Log;
- $sce: $Sce;
- comicService: ComicService;
- messageReportingService: MessageReportingService;
-
- settings: Settings;
- constants: *;
-
- messages: string[];
- missingDataInfo: string[];
- showWelcomeMessage: boolean;
- showUpdateMessage: boolean;
- isLoading: boolean;
- isUpdating: boolean;
- hasError: boolean;
- hasWarning: boolean;
-
- items: ComicItemRepository;
- allItems: ComicItemRepository;
-
- editorData: ComicEditorData;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- $sce: $Sce,
- comicService: ComicService,
- eventService: EventService,
- messageReportingService: MessageReportingService,
- latestComic: number
- ) {
- $log.debug("START ExtraController");
-
- super($scope, eventService);
-
- this.$log = $log;
- this.$sce = $sce;
- this.comicService = comicService;
- this.messageReportingService = messageReportingService;
-
- this.settings = settings;
- this.constants = constants;
- this.items = {};
- this.allItems = {};
- this.editorData = (({}: any): ComicEditorData);
- this.messages = [];
- this.missingDataInfo = [];
-
- $log.debug("END ExtraController");
- }
-
- _maintenance() {
- this._reset();
- this.messages.push(constants.messages.maintenance);
- this.messageReportingService.reportError(constants.messages.maintenance);
- this.hasWarning = true;
- }
-
- _comicDataLoading(comic: number) {
- this._loading();
- }
-
- _comicDataLoaded(comicData: ComicData) {
- this._reset();
-
- if (this.settings.values.editMode && comicData.editorData) {
- this.editorData = comicData.editorData;
- this.editorData.missing.cast.any =
- this.editorData.missing.cast.first !== null;
- this.editorData.missing.location.any =
- this.editorData.missing.location.first !== null;
- this.editorData.missing.storyline.any =
- this.editorData.missing.storyline.first !== null;
- this.editorData.missing.title.any =
- this.editorData.missing.title.first !== null;
- this.editorData.missing.tagline.any =
- this.editorData.missing.tagline.first !== null;
- this.editorData.missing.any =
- this.editorData.missing.cast.any ||
- this.editorData.missing.location.any ||
- this.editorData.missing.storyline.any ||
- this.editorData.missing.title.any ||
- this.editorData.missing.tagline.any;
-
- if (this.editorData.missing.cast.first == this.comicService.comic) {
- this.editorData.missing.cast.first = null;
- }
- if (this.editorData.missing.cast.last == this.comicService.comic) {
- this.editorData.missing.cast.last = null;
- }
-
- if (this.editorData.missing.location.first == this.comicService.comic) {
- this.editorData.missing.location.first = null;
- }
- if (this.editorData.missing.location.last == this.comicService.comic) {
- this.editorData.missing.location.last = null;
- }
-
- if (this.editorData.missing.storyline.first == this.comicService.comic) {
- this.editorData.missing.storyline.first = null;
- }
- if (this.editorData.missing.storyline.last == this.comicService.comic) {
- this.editorData.missing.storyline.last = null;
- }
- }
-
- const self = this;
- function processItem(item: ComicItem) {
- let items: ComicItem[];
- if (!self.items[item.type]) {
- items = self.items[item.type] = [];
- } else {
- items = self.items[item.type];
- }
- items.push(item);
- }
-
- function processAllItem(item: ComicItem) {
- let items: ComicItem[];
- if (!self.allItems[item.type]) {
- items = self.allItems[item.type] = [];
- } else {
- items = self.allItems[item.type];
- }
- items.push(item);
- }
-
- if (!comicData.hasData) {
- this.messages.push("This strip has no navigation data yet");
- this.hasWarning = true;
-
- if (settings.values.showAllMembers && comicData.allItems) {
- angular.forEach(comicData.allItems, processAllItem);
- }
- return;
- }
-
- let hasCast = false;
- let hasLocation = false;
- //let hasStoryline = false;
- angular.forEach(comicData.items, function (item) {
- processItem(item);
-
- if (item.type === "cast") {
- hasCast = true;
- } else if (item.type === "location") {
- hasLocation = true;
- } else if (item.type === "storyline") {
- //hasStoryline = true;
- }
- });
- if (settings.values.showAllMembers && comicData.allItems) {
- angular.forEach(comicData.allItems, processAllItem);
- }
-
- if (!hasCast && !comicData.hasNoCast) {
- this.missingDataInfo.push("cast members");
- }
- if (!hasLocation && !comicData.hasNoLocation) {
- this.missingDataInfo.push("a location");
- }
- /* #if (!hasStoryline && !comicData.hasNoStoryline) {
- self.missingDataInfo.push('a storyline');
- }*/
- if (!comicData.title && !comicData.hasNoTitle) {
- this.missingDataInfo.push("a title");
- }
- if (
- !comicData.tagline &&
- !comicData.hasNoTagline &&
- this.comicService.comic > constants.taglineThreshold
- ) {
- this.missingDataInfo.push("a tagline");
- }
-
- const currentVersion = GM.info.script.version;
- if (!settings.values.version) {
- // Version is undefined. We're a new user!
- this.$log.debug("qcExtra(): Version undefined!");
- this.showWelcomeMessage = true;
- } else if (settings.values.version !== currentVersion) {
- // Version is changed. Script has been updated!
- this.$log.debug(`qcExtra(): Version is ${settings.values.version}!`);
- this.showUpdateMessage = true;
- }
- }
-
- _comicDataError(error: any) {
- this._reset();
- if (error.status !== 503) {
- this.messages.push("Error communicating with server");
- this.hasError = true;
- } else {
- this.eventService.maintenanceEvent.publish();
- }
- }
-
- _reset() {
- this.isLoading = false;
- this.isUpdating = false;
- this.items = {};
- this.allItems = {};
- this.editorData = (({}: any): ComicEditorData);
- this.messages.length = 0;
- this.missingDataInfo.length = 0;
- this.hasError = false;
- this.hasWarning = false;
- }
-
- _loading() {
- this._reset();
- this.isLoading = true;
- this.isUpdating = true;
- this.messages.push("Loading...");
- }
-
- getTypeDescription(type: string) {
- switch (type) {
- case "cast":
- return "Cast Members";
- case "storyline":
- return "Storylines";
- case "location":
- return "Locations";
-
- case "all-cast":
- return this.$sce.trustAsHtml(
- "Cast Members " + "(Non-Present)"
- );
- case "all-storyline":
- return this.$sce.trustAsHtml(
- "Storylines " + "(Non-Present)"
- );
- case "all-location":
- return this.$sce.trustAsHtml(
- "Locations " + "(Non-Present)"
- );
- }
- }
-
- openSettings() {
- ($("#settingsDialog"): any).modal("show");
- }
-
- editComicData() {
- ($("#editComicDataDialog"): any).modal("show");
- }
-
- showEditLog() {
- ($("#editLogDialog"): any).modal("show");
- }
-
- showDetailsFor(item: ItemData) {
- $("#itemDetailsDialog").data("itemId", item.id);
- ($("#itemDetailsDialog"): any).modal("show");
- }
-
- showChangeLog() {
- this.showWelcomeMessage = false;
- this.showUpdateMessage = false;
- ($("#changeLogDialog"): any).modal("show");
- }
-}
-ExtraController.$inject = [
- "$scope",
- "$log",
- "$sce",
- "comicService",
- "eventService",
- "messageReportingService",
- "latestComic",
-];
-
-export default function (app: AngularModule) {
- app.directive("qcExtra", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: ExtraController,
- controllerAs: "e",
- template: variables.html.extra,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcExtraNavDirective.js b/assets/js/modules/angular/directives/qcExtraNavDirective.js
deleted file mode 100644
index 9d500ba..0000000
--- a/assets/js/modules/angular/directives/qcExtraNavDirective.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule } from "angular";
-
-import variables from "../../../../generated/variables.pass2";
-
-export default function (app: AngularModule) {
- app.directive("qcExtraNav", function () {
- return {
- restrict: "E",
- scope: {
- firstValue: "=",
- firstTitle: "@",
- previousValue: "=",
- previousTitle: "@",
- nextValue: "=",
- nextTitle: "@",
- lastValue: "=",
- lastTitle: "@",
- name: "@",
- nameTitle: "@",
- clickAction: "&",
- },
- template: variables.html.extraNav,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcItemDetailsDirective.js b/assets/js/modules/angular/directives/qcItemDetailsDirective.js
deleted file mode 100644
index 32a8e5c..0000000
--- a/assets/js/modules/angular/directives/qcItemDetailsDirective.js
+++ /dev/null
@@ -1,265 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import angular from "angular";
-
-import type { AngularModule, $Log } from "angular";
-
-import constants from "../../../constants";
-import settings, { Settings } from "../../settings";
-import variables from "../../../../generated/variables.pass2";
-
-import { convertDataUritoBlob } from "../util";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ColorService } from "../services/colorService";
-import type { ComicService } from "../services/comicService";
-import type { ItemService } from "../services/itemService";
-import type { MessageReportingService } from "../services/messageReportingService";
-import type { StyleService } from "../services/styleService";
-import type {
- DecoratedItemData,
- ItemRelationData,
- ItemImageData,
-} from "../api/itemData";
-
-export class ItemDetailsController {
- static $inject: string[];
-
- $log: $Log;
- $scope: $DecoratedScope;
- colorService: ColorService;
- comicService: ComicService;
- messageReportingService: MessageReportingService;
- styleService: StyleService;
- itemService: ItemService;
-
- isLoading: boolean;
- isUpdating: boolean;
- settings: Settings;
- itemData: DecoratedItemData;
-
- imagePaths: string[];
- currentImagePath: number;
- isImagePreview: boolean;
- imageFile: ?string;
- imageFileInfo: any;
-
- constructor(
- $log: $Log,
- itemService: ItemService,
- $scope: $DecoratedScope,
- colorService: ColorService,
- comicService: ComicService,
- messageReportingService: MessageReportingService,
- styleService: StyleService
- ) {
- $log.debug("START ItemDetailsController");
-
- this.$log = $log;
- this.itemService = itemService;
- this.$scope = $scope;
- this.colorService = colorService;
- this.comicService = comicService;
- this.messageReportingService = messageReportingService;
- this.styleService = styleService;
-
- this.isLoading = true;
- this.isUpdating = false;
- this.settings = settings;
-
- this.imagePaths = [];
- this.currentImagePath = 0;
- this.isImagePreview = false;
- this.imageFile = null;
- this.imageFileInfo = null;
-
- $("#itemDetailsDialog").on("show.bs.modal", () => this._getItemDetails());
-
- $log.debug("END ItemDetailsController");
- }
-
- async _getItemDetails() {
- const self = this;
- const itemId = $("#itemDetailsDialog").data("itemId");
- this.$log.debug("ItemDetailsController::showModal() - item id:", itemId);
-
- this.itemData = (({}: any): DecoratedItemData);
- this.isLoading = true;
-
- this.imagePaths = [];
- this.currentImagePath = 0;
- this.isImagePreview = false;
- this.imageFile = null;
- this.imageFileInfo = null;
-
- const itemData = await this.itemService.getItemData(itemId);
- if (itemData) {
- if (itemData.color.startsWith("#")) {
- itemData.color = itemData.color.substring(1);
- }
-
- this.$log.debug("qcItemDetails::showModal() - " + "item data:", itemData);
-
- this.$scope.safeApply(() => {
- this.itemData = itemData;
- this.isLoading = false;
- this.isUpdating = false;
-
- this.imagePaths = itemData.imageUrls;
- this.currentImagePath = 0;
-
- // If the color changes, also update the
- // highlight color
- this.$scope.$watch(
- () => {
- return this.itemData.color;
- },
- () => {
- this.itemData.highlightColor = this.colorService.createTintOrShade(
- itemData.color
- );
- }
- );
- });
- } else {
- this.close();
- }
- }
-
- _onErrorLog(response: any) {
- this.messageReportingService.reportError(response.data);
- return response;
- }
-
- _onSuccessRefreshElseErrorLog(response: any) {
- if (response.status === 200) {
- this.comicService.refreshComicData();
- } else {
- this._onErrorLog(response);
- }
- return response;
- }
-
- showInfoFor(id: number) {
- $("#itemDetailsDialog").data("itemId", id);
- this._getItemDetails();
- }
-
- keypress(event: KeyboardEvent, property: string) {
- if (event.keyCode === 13) {
- // ENTER key
- this.update(property);
- }
- }
-
- async update(property: string) {
- this.$scope.safeApply(() => {
- this.isUpdating = true;
- });
- const success = await this.itemService.updateProperty(
- this.itemData.id,
- property,
- this.itemData[property]
- );
- this.$scope.safeApply(() => {
- this.isUpdating = false;
- });
- if (success) {
- if (property === "color") {
- this.$log.debug(
- "ItemDetailsController::update() - " + "update item color"
- );
- this.styleService.removeItemStyle(this.itemData.id);
- }
- this.comicService.refreshComicData();
- this._getItemDetails();
- }
- }
-
- goToComic(comic: number) {
- this.comicService.gotoComic(comic);
- this.close();
- }
-
- close() {
- ($("#itemDetailsDialog"): any).modal("hide");
- }
-
- previewImage() {
- if (this.imageFile && this.imageFileInfo.type == "image/png") {
- this.isImagePreview = true;
- }
- }
-
- async uploadImage() {
- if (this.imageFile && this.imageFileInfo.type == "image/png") {
- const imageBlob = convertDataUritoBlob(this.imageFile);
-
- this.$scope.safeApply(() => {
- this.isUpdating = true;
- });
- const success = await this.itemService.uploadImage(
- this.itemData.id,
- imageBlob,
- this.imageFileInfo.name
- );
- if (success) {
- this._getItemDetails();
- }
- } else {
- this.messageReportingService.reportError("Only PNG images are supported");
- }
- }
-
- previousImage() {
- this.currentImagePath--;
- if (this.currentImagePath < 0) {
- this.currentImagePath = 0;
- }
- }
-
- nextImage() {
- this.currentImagePath++;
- if (this.currentImagePath >= this.imagePaths.length) {
- this.currentImagePath = this.imagePaths.length - 1;
- }
- }
-}
-ItemDetailsController.$inject = [
- "$log",
- "itemService",
- "$scope",
- "colorService",
- "comicService",
- "messageReportingService",
- "styleService",
-];
-
-export default function (app: AngularModule) {
- app.directive("qcItemDetails", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: ItemDetailsController,
- controllerAs: "idvm",
- template: variables.html.itemDetails,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcNavDirective.js b/assets/js/modules/angular/directives/qcNavDirective.js
deleted file mode 100644
index 481ba96..0000000
--- a/assets/js/modules/angular/directives/qcNavDirective.js
+++ /dev/null
@@ -1,118 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import settings, { Settings } from "../../settings";
-import variables from "../../../../generated/variables.pass2";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-
-export class NavController {
- static $inject: string[];
-
- $scope: $DecoratedScope;
- comicService: ComicService;
- latestComic: number;
- randomComic: number;
- mainDirective: boolean;
-
- settings: Settings;
-
- constructor(
- $scope: $DecoratedScope,
- comicService: ComicService,
- latestComic: number
- ) {
- this.$scope = $scope;
- this.comicService = comicService;
- this.latestComic = latestComic;
-
- this.settings = settings;
-
- if (this.$scope.mainDirective) {
- $scope.$watchGroup(
- [
- () => {
- return this.settings.values.skipGuest;
- },
- () => {
- return this.settings.values.skipNonCanon;
- },
- ],
- () => {
- this._updateRandomComic();
- }
- );
- }
- }
-
- async _updateRandomComic() {
- this.$scope.randomComic = this.comicService.nextRandomComic();
- const randomComic = await this.comicService.nextFilteredRandomComic();
- this.$scope.safeApply(() => {
- this.$scope.randomComic = randomComic;
- });
- }
-
- first(event: UIEvent) {
- event.preventDefault();
- event.stopPropagation();
- this.comicService.first();
- }
-
- previous(event: UIEvent) {
- event.preventDefault();
- event.stopPropagation();
- this.comicService.previous();
- }
-
- next(event: UIEvent) {
- event.preventDefault();
- event.stopPropagation();
- this.comicService.next();
- }
-
- last(event: UIEvent) {
- event.preventDefault();
- event.stopPropagation();
- this.comicService.last();
- }
-
- random(event: UIEvent) {
- event.preventDefault();
- event.stopPropagation();
-
- this.comicService.gotoComic(this.$scope.randomComic);
- this._updateRandomComic();
- }
-}
-NavController.$inject = ["$scope", "comicService", "latestComic"];
-
-export default function (app: AngularModule) {
- app.directive("qcNav", function () {
- return {
- restrict: "E",
- scope: { randomComic: "=", mainDirective: "=" },
- controller: NavController,
- controllerAs: "n",
- template: variables.html.navigation,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcNewsDirective.js b/assets/js/modules/angular/directives/qcNewsDirective.js
deleted file mode 100644
index 14dfc4a..0000000
--- a/assets/js/modules/angular/directives/qcNewsDirective.js
+++ /dev/null
@@ -1,79 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log, $Sce } from "angular";
-
-import constants from "../../../constants";
-import { nl2br, angularizeLinks } from "../util";
-
-import { EventHandlingControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { EventService } from "../services/eventService";
-import type { ComicData } from "../api/comicData";
-
-export class NewsController extends EventHandlingControllerBase {
- static $inject: string[];
-
- $sce: $Sce;
-
- news: string;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- eventService: EventService,
- $sce: $Sce
- ) {
- $log.debug("START NewsController");
-
- super($scope, eventService);
- this.$sce = $sce;
-
- this.news = $sce.trustAsHtml("Loading...");
-
- $log.debug("END NewsController");
- }
-
- _comicDataLoading(comic: number) {
- this.news = this.$sce.trustAsHtml("Loading...");
- }
-
- _comicDataLoaded(comicData: ComicData) {
- const news = comicData.news;
- if (news == null) {
- this.news = "";
- } else {
- this.news = this.$sce.trustAsHtml(nl2br(angularizeLinks(news), false));
- }
- }
-}
-NewsController.$inject = ["$scope", "$log", "eventService", "$sce"];
-
-export default function (app: AngularModule) {
- app.directive("qcNews", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: NewsController,
- controllerAs: "n",
- template: '',
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcRibbonDirective.js b/assets/js/modules/angular/directives/qcRibbonDirective.js
deleted file mode 100644
index 2836e05..0000000
--- a/assets/js/modules/angular/directives/qcRibbonDirective.js
+++ /dev/null
@@ -1,74 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import constants from "../../../constants";
-import settings, { Settings } from "../../settings";
-import variables from "../../../../generated/variables.pass2";
-
-import { EventHandlingControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { EventService } from "../services/eventService";
-import type { ComicData } from "../api/comicData";
-
-export class RibbonController extends EventHandlingControllerBase {
- static $inject: string[];
-
- settings: Settings;
- isNonCanon: ?boolean;
- isGuestComic: ?boolean;
- isSmall: boolean;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- eventService: EventService
- ) {
- $log.debug("START RibbonController");
-
- super($scope, eventService);
-
- this.settings = settings;
- this.isNonCanon = false;
- this.isGuestComic = false;
- this.isSmall = settings.values.showSmallRibbonByDefault;
-
- $log.debug("END RibbonController");
- }
-
- _comicDataLoaded(comicData: ComicData) {
- this.isNonCanon = comicData.isNonCanon;
- this.isGuestComic = comicData.isGuestComic;
- }
-}
-RibbonController.$inject = ["$scope", "$log", "eventService"];
-
-export default function (app: AngularModule) {
- app.directive("qcRibbon", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: RibbonController,
- controllerAs: "r",
- template: variables.html.ribbon,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcSetPublishDateDirective.js b/assets/js/modules/angular/directives/qcSetPublishDateDirective.js
deleted file mode 100644
index 74a6d61..0000000
--- a/assets/js/modules/angular/directives/qcSetPublishDateDirective.js
+++ /dev/null
@@ -1,124 +0,0 @@
-// @flow
-
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import constants from "../../../constants";
-import variables from "../../../../generated/variables.pass2";
-
-import { SetValueControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-import type { EventService } from "../services/eventService";
-import type { MessageReportingService } from "../services/messageReportingService";
-import type { ComicData } from "../api/comicData";
-
-export class SetPublishDateController extends SetValueControllerBase {
- static $inject: string[];
-
- $log: $Log;
-
- messageReportingService: MessageReportingService;
-
- publishDate: ?Date;
- isAccuratePublishDate: ?boolean;
-
- isUpdating: boolean;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- comicService: ComicService,
- eventService: EventService,
- messageReportingService: MessageReportingService
- ) {
- $log.debug("START SetPublishDateController");
-
- super($scope, comicService, eventService);
- this.$log = $log;
- this.messageReportingService = messageReportingService;
-
- this.publishDate = new Date();
-
- $log.debug("END SetPublishDateController");
- }
-
- _comicDataLoaded(comicData: ComicData) {
- if (comicData.publishDate != null) {
- this.publishDate = new Date(comicData.publishDate);
- } else {
- this.publishDate = null;
- }
- this.isAccuratePublishDate = comicData.isAccuratePublishDate;
- this.$scope.isUpdating = false;
- }
-
- _updateValue() {
- this.setPublishDate(false);
- }
-
- setAccuratePublishDate() {
- this.setPublishDate(true);
- }
-
- async setPublishDate(setAccurate: boolean) {
- if (this.publishDate == null) {
- // Error
- this.messageReportingService.reportWarning(
- "The date entered is not valid!"
- );
- return;
- }
-
- this.$scope.isUpdating = true;
- const response = await this.comicService.setPublishDate(
- this.publishDate,
- this.isAccuratePublishDate != null ? this.isAccuratePublishDate : false
- );
- if (response.status !== 200) {
- this.$scope.safeApply(() => {
- this.$scope.isUpdating = false;
- if (setAccurate) {
- this.isAccuratePublishDate = !this.isAccuratePublishDate;
- }
- });
- }
- }
-}
-SetPublishDateController.$inject = [
- "$scope",
- "$log",
- "comicService",
- "eventService",
- "messageReportingService",
-];
-
-export default function (app: AngularModule) {
- app.directive("qcSetPublishDate", function () {
- return {
- restrict: "E",
- replace: true,
- scope: { isUpdating: "=" },
- controller: SetPublishDateController,
- controllerAs: "s",
- template: variables.html.setPublishDate,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcSetTaglineDirective.js b/assets/js/modules/angular/directives/qcSetTaglineDirective.js
deleted file mode 100644
index 207c7cd..0000000
--- a/assets/js/modules/angular/directives/qcSetTaglineDirective.js
+++ /dev/null
@@ -1,95 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import constants from "../../../constants";
-import variables from "../../../../generated/variables.pass2";
-
-import { SetValueControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-import type { EventService } from "../services/eventService";
-import type { ComicData } from "../api/comicData";
-
-export class SetTaglineController extends SetValueControllerBase {
- static $inject: string[];
-
- $log: $Log;
-
- tagline: ?string;
-
- isUpdating: boolean;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- comicService: ComicService,
- eventService: EventService
- ) {
- $log.debug("START SetTaglineController");
-
- super($scope, comicService, eventService);
- this.$log = $log;
-
- this.tagline = "";
-
- $log.debug("END SetTaglineController");
- }
-
- _comicDataLoaded(comicData: ComicData) {
- this.tagline = comicData.tagline;
- this.$scope.isUpdating = false;
- }
-
- _updateValue() {
- this.setTagline();
- }
-
- async setTagline() {
- this.$scope.isUpdating = true;
- const response = await this.comicService.setTagline(
- this.tagline ? this.tagline : ""
- );
- if (response.status !== 200) {
- this.$scope.safeApply(() => {
- this.$scope.isUpdating = false;
- });
- }
- }
-}
-SetTaglineController.$inject = [
- "$scope",
- "$log",
- "comicService",
- "eventService",
-];
-
-export default function (app: AngularModule) {
- app.directive("qcSetTagline", function () {
- return {
- restrict: "E",
- replace: true,
- scope: { isUpdating: "=" },
- controller: SetTaglineController,
- controllerAs: "s",
- template: variables.html.setTagline,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcSetTitleDirective.js b/assets/js/modules/angular/directives/qcSetTitleDirective.js
deleted file mode 100644
index 103b30c..0000000
--- a/assets/js/modules/angular/directives/qcSetTitleDirective.js
+++ /dev/null
@@ -1,90 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import constants from "../../../constants";
-import variables from "../../../../generated/variables.pass2";
-
-import { SetValueControllerBase } from "../controllers/ControllerBases";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-import type { EventService } from "../services/eventService";
-import type { ComicData } from "../api/comicData";
-
-export class SetTitleController extends SetValueControllerBase {
- static $inject: string[];
-
- $log: $Log;
-
- title: ?string;
-
- isUpdating: boolean;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- comicService: ComicService,
- eventService: EventService
- ) {
- $log.debug("START SetTitleController");
-
- super($scope, comicService, eventService);
- this.$log = $log;
-
- this.title = "";
-
- $log.debug("END SetTitleController");
- }
-
- _comicDataLoaded(comicData: ComicData) {
- this.title = comicData.title;
- this.$scope.isUpdating = false;
- }
-
- _updateValue() {
- this.setTitle();
- }
-
- async setTitle() {
- this.$scope.isUpdating = true;
- const response = await this.comicService.setTitle(
- this.title ? this.title : ""
- );
- if (response.status !== 200) {
- this.$scope.safeApply(() => {
- this.$scope.isUpdating = false;
- });
- }
- }
-}
-SetTitleController.$inject = ["$scope", "$log", "comicService", "eventService"];
-
-export default function (app: AngularModule) {
- app.directive("qcSetTitle", function () {
- return {
- restrict: "E",
- replace: true,
- scope: { isUpdating: "=" },
- controller: SetTitleController,
- controllerAs: "s",
- template: variables.html.setTitle,
- };
- });
-}
diff --git a/assets/js/modules/angular/directives/qcSettingsDirective.js b/assets/js/modules/angular/directives/qcSettingsDirective.js
deleted file mode 100644
index e4f6d24..0000000
--- a/assets/js/modules/angular/directives/qcSettingsDirective.js
+++ /dev/null
@@ -1,94 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-import settings, { Settings } from "../../settings";
-import variables from "../../../../generated/variables.pass2";
-
-import type { $DecoratedScope } from "../decorateScope";
-import type { ComicService } from "../services/comicService";
-
-export class SettingsController {
- static $inject: string[];
-
- $scope: $DecoratedScope;
- $log: $Log;
- comicService: ComicService;
-
- settings: Settings;
-
- constructor(
- $scope: $DecoratedScope,
- $log: $Log,
- comicService: ComicService
- ) {
- $log.debug("START SettingsController");
-
- this.$scope = $scope;
- this.$log = $log;
- this.comicService = comicService;
-
- this.settings = settings;
-
- $scope.$watchGroup(
- [
- () => {
- return this.settings.values.showAllMembers;
- },
- () => {
- return this.settings.values.editMode;
- },
- ],
- () => {
- this.comicService.refreshComicData();
- }
- );
-
- $("#settingsDialog").on("hide.bs.modal", async () => {
- $log.debug("Saving settings...");
- await this.settings.saveSettings();
- $log.debug("Settings saved.");
- });
-
- $log.debug("END SettingsController");
- }
-
- close() {
- ($("#settingsDialog"): any).modal("hide");
- }
-
- showChangeLog() {
- this.close();
- ($("#changeLogDialog"): any).modal("show");
- }
-}
-SettingsController.$inject = ["$scope", "$log", "comicService"];
-
-export default function (app: AngularModule) {
- app.directive("qcSettings", function () {
- return {
- restrict: "E",
- replace: true,
- scope: {},
- controller: SettingsController,
- controllerAs: "svm",
- template: variables.html.settings,
- };
- });
-}
diff --git a/assets/js/modules/angular/run.js b/assets/js/modules/angular/run.js
deleted file mode 100644
index f3a968b..0000000
--- a/assets/js/modules/angular/run.js
+++ /dev/null
@@ -1,33 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule } from "angular";
-
-import settings from "./../settings";
-
-export default function (app: AngularModule) {
- app.run([
- "$rootScope",
- "comicService",
- "startComic",
- function ($rootScope, comicService, startComic) {
- $rootScope.settings = settings;
- comicService.gotoComic(startComic);
- },
- ]);
-}
diff --git a/assets/js/modules/angular/scopes/rootScope.js b/assets/js/modules/angular/scopes/rootScope.js
deleted file mode 100644
index f532188..0000000
--- a/assets/js/modules/angular/scopes/rootScope.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-export type RootScope = {};
diff --git a/assets/js/modules/angular/services/colorService.js b/assets/js/modules/angular/services/colorService.js
deleted file mode 100644
index 6ff796d..0000000
--- a/assets/js/modules/angular/services/colorService.js
+++ /dev/null
@@ -1,304 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import type { AngularModule, $Log } from "angular";
-
-// Parts based on code from:
-// http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
-
-function hue2rgb(p, q, t) {
- if (t < 0) {
- t += 1;
- }
-
- if (t > 1) {
- t -= 1;
- }
-
- if (t < 1 / 6) {
- return p + (q - p) * 6 * t;
- }
-
- if (t < 1 / 2) {
- return q;
- }
-
- if (t < 2 / 3) {
- return p + (q - p) * (2 / 3 - t) * 6;
- }
-
- return p;
-}
-
-type HSLValue = [number, number, number];
-type RGBValue = [number, number, number];
-type HSVValue = [number, number, number];
-
-export class ColorService {
- $log: $Log;
-
- constructor($log: $Log) {
- this.$log = $log;
- }
-
- /**
- * Converts an RGB color value to HSL. Conversion formula
- * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
- * Assumes r, g, and b are contained in the set [0, 255] and
- * returns h, s, and l in the set [0, 1].
- *
- * @param {number} r The red color value
- * @param {number} g The green color value
- * @param {number} b The blue color value
- * @return {HSLValue} The HSL representation
- */
- rgbToHsl(r: number, g: number, b: number): HSLValue {
- r /= 255;
- g /= 255;
- b /= 255;
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- let h = 0;
- let s;
- const l = (max + min) / 2;
-
- if (max === min) {
- h = s = 0; // Achromatic
- } else {
- const d = max - min;
-
- s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
-
- switch (max) {
- case r:
- h = (g - b) / d + (g < b ? 6 : 0);
- break;
-
- case g:
- h = (b - r) / d + 2;
- break;
-
- case b:
- h = (r - g) / d + 4;
- break;
- }
- h /= 6;
- }
-
- return [h, s, l];
- }
-
- /**
- * Converts an HSL color value to RGB. Conversion formula
- * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
- * Assumes h, s, and l are contained in the set [0, 1] and
- * returns r, g, and b in the set [0, 255].
- *
- * @param {number} h The hue
- * @param {number} s The saturation
- * @param {number} l The lightness
- * @return {RGBValue} The RGB representation
- */
- hslToRgb(h: number, s: number, l: number): RGBValue {
- let r;
- let g;
- let b;
-
- if (s === 0) {
- r = g = b = l; // Achromatic
- } else {
- const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
- const p = 2 * l - q;
-
- r = hue2rgb(p, q, h + 1 / 3);
- g = hue2rgb(p, q, h);
- b = hue2rgb(p, q, h - 1 / 3);
- }
-
- return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
- }
-
- /**
- * Converts an RGB color value to HSV. Conversion formula
- * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
- * Assumes r, g, and b are contained in the set [0, 255] and
- * returns h, s, and v in the set [0, 1].
- *
- * @param {number} r The red color value
- * @param {number} g The green color value
- * @param {number} b The blue color value
- * @return {HSVValue} The HSV representation
- */
- rgbToHsv(r: number, g: number, b: number): HSVValue {
- r = r / 255;
- g = g / 255;
- b = b / 255;
- const max = Math.max(r, g, b);
- const min = Math.min(r, g, b);
- let h = 0;
- let s;
- const v = max;
-
- const d = max - min;
-
- s = max === 0 ? 0 : d / max;
-
- if (max === min) {
- h = 0; // Achromatic
- } else {
- switch (max) {
- case r:
- h = (g - b) / d + (g < b ? 6 : 0);
- break;
-
- case g:
- h = (b - r) / d + 2;
- break;
-
- case b:
- h = (r - g) / d + 4;
- break;
- }
- h /= 6;
- }
-
- return [h, s, v];
- }
-
- /**
- * Converts an HSV color value to RGB. Conversion formula
- * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
- * Assumes h, s, and v are contained in the set [0, 1] and
- * returns r, g, and b in the set [0, 255].
- *
- * @param {number} h The hue
- * @param {number} s The saturation
- * @param {number} v The value
- * @return {RGBValue} The RGB representation
- */
- hsvToRgb(h: number, s: number, v: number) {
- let r = 0;
- let g = 0;
- let b = 0;
-
- const i = Math.floor(h * 6);
- const f = h * 6 - i;
- const p = v * (1 - s);
- const q = v * (1 - f * s);
- const t = v * (1 - (1 - f) * s);
-
- switch (i % 6) {
- case 0:
- r = v;
- g = t;
- b = p;
- break;
-
- case 1:
- r = q;
- g = v;
- b = p;
- break;
-
- case 2:
- r = p;
- g = v;
- b = t;
- break;
-
- case 3:
- r = p;
- g = q;
- b = v;
- break;
-
- case 4:
- r = t;
- g = p;
- b = v;
- break;
-
- case 5:
- r = v;
- g = p;
- b = q;
- break;
- }
-
- return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
- }
-
- hexColorToRgb(hexColor: string): RGBValue {
- if (hexColor.charAt(0) === "#") {
- hexColor = hexColor.substring(1); // Strip #
- }
- const rgb = parseInt(hexColor, 16); // Convert rrggbb to decimal
- const r = (rgb >> 16) & 0xff; // Extract red
- const g = (rgb >> 8) & 0xff; // Extract green
- const b = rgb & 0xff; // Extract blue
- return [r, g, b];
- }
-
- rgbToHexColor(r: number, g: number, b: number): string {
- return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
- }
-
- getRgbRelativeLuminance(r: number, g: number, b: number): number {
- return 0.2126 * r + 0.7152 * g + 0.0722 * b; // Per ITU-R BT.709
- }
-
- createTintOrShade(hexColor: string, iterations?: number): string {
- if (typeof iterations === "undefined") {
- iterations = 1;
- }
-
- let rgb = this.hexColorToRgb(hexColor);
- const hsl = this.rgbToHsl(rgb[0], rgb[1], rgb[2]);
-
- const tint = hsl[2] < 0.5;
-
- for (let i = iterations; i > 0; i--) {
- // If it's a dark color, make it lighter
- // and vice versa.
- if (tint) {
- // Increase the lightness by
- // 50% (tint)
- hsl[2] = (hsl[2] + 1) / 2;
- } else {
- // Decrease the lightness by
- // 50% (shade)
- hsl[2] /= 2;
- }
- }
-
- rgb = this.hslToRgb(hsl[0], hsl[1], hsl[2]);
- return this.rgbToHexColor(rgb[0], rgb[1], rgb[2]);
- }
-}
-
-export default function (app: AngularModule) {
- app.service("colorService", [
- "$log",
- function ($log: $Log) {
- $log.debug("START colorService()");
- const colorService = new ColorService($log);
- $log.debug("END colorService()");
- return colorService;
- },
- ]);
-}
diff --git a/assets/js/modules/angular/services/comicService.js b/assets/js/modules/angular/services/comicService.js
deleted file mode 100644
index b65e505..0000000
--- a/assets/js/modules/angular/services/comicService.js
+++ /dev/null
@@ -1,503 +0,0 @@
-// @flow
-/*
- * Copyright (C) 2016-2022 Alexander Krivács Schrøder
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-import $ from "jquery";
-import angular from "angular";
-import type {
- AngularModule,
- $Log,
- $Scope,
- AngularHttpService,
- $Location,
-} from "angular";
-import type { $StateParams } from "angular-ui";
-
-import constants from "../../../constants";
-import settings from "../../settings";
-
-import type { EventService } from "./eventService";
-import type { ColorService } from "./colorService";
-import type { StyleService } from "./styleService";
-import type { MessageReportingService } from "./messageReportingService";
-import type { ComicData, ComicItem } from "../api/comicData.js";
-
-export class ComicService {
- $log: $Log;
- $stateParams: $StateParams;
- $location: $Location;
- $scope: $Scope