├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── import-resolver-webpack.conf.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── public ├── index.html ├── robots.txt └── static │ └── icons │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon-152x152.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ ├── manifest.json │ ├── msapplication-icon-144x144.png │ └── safari-pinned-tab.svg ├── src ├── App.vue ├── assets │ ├── logo.png │ └── social │ │ ├── github.svg │ │ ├── linkedin.svg │ │ ├── logo.svg │ │ ├── weibo.svg │ │ └── zhihu.svg ├── components │ ├── about │ │ ├── about.vue │ │ ├── card.vue │ │ ├── contributor.vue │ │ ├── copyright.vue │ │ ├── dev.vue │ │ ├── introduction.vue │ │ └── why.vue │ ├── article │ │ ├── basic │ │ │ ├── app.vue │ │ │ ├── edit.vue │ │ │ ├── toggle-article-vote.js │ │ │ └── update-record.js │ │ ├── detail │ │ │ └── app.vue │ │ ├── edit │ │ │ ├── home_article_setting.vue │ │ │ └── user_article_setting.vue │ │ ├── home │ │ │ ├── create.vue │ │ │ ├── detail.vue │ │ │ ├── edit.vue │ │ │ └── tile.vue │ │ ├── list │ │ │ ├── app.vue │ │ │ └── list.vue │ │ └── user │ │ │ ├── create.vue │ │ │ ├── detail.vue │ │ │ ├── edit.vue │ │ │ └── tile.vue │ ├── async │ │ ├── code-mirror │ │ │ ├── app.vue │ │ │ ├── loading-wrapper.vue │ │ │ └── settings.vue │ │ └── mixrend │ │ │ ├── app.vue │ │ │ └── index.js │ ├── basic │ │ └── texteditor.vue │ ├── chart │ │ ├── bar.vue │ │ ├── doughnut.vue │ │ └── line.vue │ ├── comments │ │ ├── app.vue │ │ ├── comment.vue │ │ ├── comments.vue │ │ ├── editor.vue │ │ ├── reply-comment.vue │ │ ├── toggle-reply-vote.js │ │ └── update.vue │ ├── contest │ │ ├── detail │ │ │ ├── app.vue │ │ │ ├── clarification.vue │ │ │ ├── problem.vue │ │ │ ├── rank.vue │ │ │ ├── submission.vue │ │ │ ├── submit.vue │ │ │ └── summary.vue │ │ ├── edit │ │ │ ├── create.vue │ │ │ ├── preview.vue │ │ │ └── update.vue │ │ ├── list │ │ │ ├── app.vue │ │ │ └── list.vue │ │ ├── review │ │ │ ├── app.vue │ │ │ ├── dialog.vue │ │ │ ├── mine.vue │ │ │ ├── overall.vue │ │ │ ├── query-dialog.vue │ │ │ ├── team-preview.vue │ │ │ ├── team-tile.vue │ │ │ └── update-team.vue │ │ ├── submission │ │ │ ├── detail.vue │ │ │ └── summary.vue │ │ └── utils.js │ ├── footer │ │ └── app.vue │ ├── global │ │ └── 404.vue │ ├── home │ │ ├── announcement.vue │ │ ├── blog.vue │ │ ├── curtain.vue │ │ ├── develop.vue │ │ ├── guide.vue │ │ ├── home.vue │ │ ├── honor.vue │ │ └── recently.vue │ ├── language │ │ └── utils │ │ │ └── select.vue │ ├── navigation │ │ ├── app.vue │ │ ├── drawer.vue │ │ ├── toolbar.vue │ │ ├── user-menu-mobile.vue │ │ └── user-menu.vue │ ├── problem │ │ ├── create │ │ │ └── app.vue │ │ ├── detail │ │ │ ├── app.vue │ │ │ ├── description.vue │ │ │ ├── editor.vue │ │ │ └── preview.vue │ │ ├── edit │ │ │ ├── app.vue │ │ │ └── setting.vue │ │ ├── list │ │ │ ├── app.vue │ │ │ └── list.vue │ │ └── utils │ │ │ ├── auto-complete.vue │ │ │ ├── fetch.js │ │ │ └── preview.vue │ ├── signin │ │ ├── login.vue │ │ ├── signout.vue │ │ └── signup.vue │ ├── snackbar │ │ └── app.vue │ ├── status │ │ ├── detail │ │ │ ├── app.vue │ │ │ ├── code.vue │ │ │ ├── connection.js │ │ │ ├── progress.vue │ │ │ └── summary.vue │ │ └── list │ │ │ ├── app.vue │ │ │ └── list.vue │ ├── user │ │ ├── detail │ │ │ ├── app.vue │ │ │ ├── layout.vue │ │ │ ├── profile.vue │ │ │ ├── solved.vue │ │ │ └── summary.vue │ │ ├── list │ │ │ ├── app.vue │ │ │ └── list.vue │ │ ├── settings │ │ │ └── app.vue │ │ └── utils │ │ │ └── auto-complete.vue │ ├── utils │ │ ├── code-mirror.vue │ │ ├── error-spinner.vue │ │ ├── loading-spinner.vue │ │ ├── random-color.js │ │ └── search-bar.vue │ └── verdict │ │ └── utils │ │ └── select.vue ├── docs │ ├── develop.md │ └── honor.md ├── graphql │ ├── article │ │ ├── create.gql │ │ ├── detail.gql │ │ ├── discussion.gql │ │ └── list.gql │ ├── home │ │ ├── home-article-list.gql │ │ ├── topuser.gql │ │ └── user-article-list.gql │ ├── language │ │ └── languagelist.gql │ ├── problem │ │ ├── detail.gql │ │ ├── edit-detail.gql │ │ ├── edit.gql │ │ ├── list.gql │ │ └── search.gql │ ├── signin │ │ ├── register.gql │ │ └── token.gql │ ├── submission │ │ ├── list.gql │ │ └── submit.gql │ ├── user │ │ ├── list.gql │ │ ├── profile.gql │ │ ├── search.gql │ │ └── settings.gql │ ├── utils │ │ └── uploadimage.gql │ └── votediscussion │ │ └── vote.gql ├── main.js ├── modules │ ├── code-mirror │ │ ├── keymap.js │ │ └── theme.js │ ├── language │ │ └── main.js │ └── verdict │ │ └── main.js ├── npm-debug.log ├── plugins │ ├── essential │ │ ├── apollo-provider.js │ │ ├── auth.js │ │ ├── filters.js │ │ ├── global-components.js │ │ ├── graphql-tag.js │ │ ├── router.js │ │ ├── store.js │ │ ├── vue-line-clamp.js │ │ ├── vue-moment.js │ │ ├── vue.js │ │ └── vuetify.js │ ├── external │ │ ├── code-mirror.js │ │ ├── markdown-it-katex.js │ │ └── markdown-it-vue.js │ ├── index.js │ └── npm-debug.log ├── registerServiceWorker.js ├── router │ ├── about │ │ └── router.js │ ├── article │ │ └── router.js │ ├── contest │ │ └── router.js │ ├── home │ │ └── router.js │ ├── index.js │ ├── problem │ │ └── router.js │ ├── sign │ │ └── router.js │ ├── status │ │ └── router.js │ ├── user │ │ └── router.js │ └── utils.js ├── store │ ├── actions.js │ ├── index.js │ ├── modules │ │ ├── editor.js │ │ ├── footer.js │ │ ├── navbar.js │ │ ├── snackbar.js │ │ └── user.js │ └── mutations.js ├── stylus │ └── main.styl └── utils.js └── vue.config.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: 'babel-eslint', 5 | }, 6 | env: { 7 | browser: true, 8 | }, 9 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 10 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` 11 | // for stricter rules. 12 | extends: [ 13 | 'plugin:vue/recommended', 14 | '@vue/airbnb', 15 | ], 16 | plugins: [ 17 | 'graphql', 18 | ], 19 | // check if imports actually resolve 20 | settings: { 21 | 'import/resolver': { 22 | webpack: { 23 | config: 'import-resolver-webpack.conf.js', 24 | }, 25 | }, 26 | }, 27 | // add your custom rules here 28 | rules: { 29 | // don't require .vue extension when importing 30 | 'import/extensions': ['error', 'always', { 31 | js: 'never', 32 | mjs: 'never', 33 | vue: 'never', 34 | }], 35 | // disallow reassignment of function parameters 36 | // disallow parameter object manipulation except for specific exclusions 37 | 'no-param-reassign': ['error', { 38 | props: true, 39 | ignorePropertyModificationsFor: [ 40 | 'state', // for vuex state 41 | 'acc', // for reduce accumulators 42 | 'e', // for e.returnvalue 43 | ], 44 | }], 45 | // allow optionalDependencies 46 | 'import/no-extraneous-dependencies': ['error', { 47 | optionalDependencies: ['test/unit/index.js'], 48 | }], 49 | // allow debugger during development 50 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 51 | 'vue/html-indent': [ 52 | 'error', 53 | 'tab', 54 | ], 55 | indent: [ 56 | 'error', 57 | 'tab', 58 | ], 59 | 'no-tabs': 0, 60 | 'no-unused-vars': [ 61 | 'error', 62 | { argsIgnorePattern: '^_' }, 63 | ], 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore dist file 2 | dist/ 3 | 4 | # ignore node_modules 5 | node_modules/ 6 | 7 | # ignore vscode setting file 8 | .vscode/ 9 | 10 | # ignore idea setting file 11 | .idea/ 12 | 13 | *nohup.out -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lutece-frontend 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ## Advanced 24 | 25 | You can run scripts with additional features using the GUI. 26 | 27 | ### Install Vue CLI 28 | ``` 29 | npm install -g @vue/cli 30 | ``` 31 | 32 | ### Run Vue CLI GUI 33 | ``` 34 | vue ui 35 | ``` 36 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* eslint no-template-curly-in-string: "off" */ 2 | module.exports = { 3 | presets: [ 4 | [ 5 | '@vue/app', 6 | { 7 | useBuiltIns: 'entry', 8 | }, 9 | ], 10 | ], 11 | plugins: [ 12 | [ 13 | 'transform-imports', 14 | { 15 | vuetify: { 16 | transform: 'vuetify/es5/components/${member}', 17 | preventFullImport: true, 18 | }, 19 | }, 20 | ], 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /import-resolver-webpack.conf.js: -------------------------------------------------------------------------------- 1 | require('babel-polyfill'); 2 | 3 | const path = require('path'); 4 | 5 | module.exports = { 6 | entry: ['babel-polyfill', './src/main.js'], 7 | 8 | resolve: { 9 | extensions: ['.js', '.vue', '.json'], 10 | alias: { 11 | vue$: 'vue/dist/vue.runtime.esm.js', 12 | '@': path.resolve(__dirname, 'src'), 13 | }, 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /\.styl(us)?$/, 20 | use: ['vue-style-loader', 'css-loader', 'stylus-loader'], 21 | }, 22 | { 23 | test: /.md$/, 24 | use: 'text-loader', 25 | }, 26 | ], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "baseUrl": ".", 5 | "paths": { 6 | "@/*": [ 7 | "src/*" 8 | ] 9 | }, 10 | "sourceMap": true 11 | }, 12 | "include": [ 13 | "./src/**/*" 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | "dist" 18 | ] 19 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lutece-frontend", 3 | "version": "0.3.1", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "@babel/polyfill": "^7.4.0", 12 | "@mdi/font": "^3.5.95", 13 | "apollo-cache-inmemory": "^1.5.1", 14 | "apollo-client": "^2.5.1", 15 | "apollo-link-context": "^1.0.17", 16 | "apollo-upload-client": "^10.0.0", 17 | "babel-polyfill": "^6.26.0", 18 | "chart.js": "^2.8.0", 19 | "codemirror": "^5.45.0", 20 | "katex": "^0.10.1", 21 | "lodash.debounce": "^4.0.8", 22 | "markdown-it": "^8.4.2", 23 | "markdown-it-texmath": "^0.5.5", 24 | "register-service-worker": "^1.6.2", 25 | "unfetch": "^4.1.0", 26 | "vue": "^2.6.10", 27 | "vue-apollo": "^3.0.0-beta.28", 28 | "vue-chartjs": "^3.4.2", 29 | "vue-codemirror": "^4.0.6", 30 | "vue-line-clamp": "^1.3.2", 31 | "vue-meta": "^1.5.8", 32 | "vue-moment": "^4.1.0", 33 | "vue-router": "^3.0.2", 34 | "vuetify": "^1.5.7", 35 | "vuetify-datetime-picker": "^1.0.13", 36 | "vuex": "^3.1.0" 37 | }, 38 | "devDependencies": { 39 | "@vue/cli-plugin-babel": "^3.5.0", 40 | "@vue/cli-plugin-eslint": "^3.5.0", 41 | "@vue/cli-plugin-pwa": "^3.5.0", 42 | "@vue/cli-service": "^3.5.3", 43 | "@vue/eslint-config-airbnb": "^4.0.0", 44 | "babel-plugin-transform-imports": "^1.5.1", 45 | "eslint-plugin-graphql": "^3.0.3", 46 | "fontmin-webpack": "^2.0.3", 47 | "glob-all": "^3.1.0", 48 | "graphql-tag": "^2.10.1", 49 | "lint-staged": "^8.1.5", 50 | "lodash": "^4.17.11", 51 | "purgecss-webpack-plugin": "^1.4.0", 52 | "raw-loader": "^1.0.0", 53 | "stylus": "^0.54.5", 54 | "stylus-loader": "^3.0.2", 55 | "vue-cli-plugin-apollo": "^0.19.1", 56 | "vue-cli-plugin-vuetify": "^0.4.6", 57 | "vue-cli-plugin-webpack-bundle-analyzer": "^1.2.0", 58 | "vue-template-compiler": "^2.6.10", 59 | "webpack-cli": "^3.3.0" 60 | }, 61 | "gitHooks": { 62 | "pre-commit": "lint-staged" 63 | }, 64 | "lint-staged": { 65 | "*.js": [ 66 | "vue-cli-service lint", 67 | "git add" 68 | ], 69 | "*.vue": [ 70 | "vue-cli-service lint", 71 | "git add" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Lutece 10 | 11 | 12 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/static/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/public/static/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/static/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/public/static/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/static/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/public/static/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/static/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/public/static/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/static/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/public/static/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/static/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/public/static/icons/favicon.ico -------------------------------------------------------------------------------- /public/static/icons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lutece", 3 | "short_name": "Lutece", 4 | "icons": [ 5 | { 6 | "src": "/static/icons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/static/icons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "start_url": "/home", 17 | "display": "standalone", 18 | "background_color": "#FFFFFF", 19 | "theme_color": "#1E88E5" 20 | } 21 | -------------------------------------------------------------------------------- /public/static/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/public/static/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /public/static/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.11, written by Peter Selinger 2001-2013 9 | 10 | 12 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 46 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/social/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/social/linkedin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/social/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/social/weibo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/social/zhihu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/about/about.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 46 | -------------------------------------------------------------------------------- /src/components/about/card.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 51 | 62 | -------------------------------------------------------------------------------- /src/components/about/contributor.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 64 | -------------------------------------------------------------------------------- /src/components/about/copyright.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 26 | 31 | -------------------------------------------------------------------------------- /src/components/about/dev.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 35 | 63 | -------------------------------------------------------------------------------- /src/components/about/introduction.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | 32 | -------------------------------------------------------------------------------- /src/components/about/why.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 79 | -------------------------------------------------------------------------------- /src/components/article/basic/app.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 68 | -------------------------------------------------------------------------------- /src/components/article/basic/edit.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | -------------------------------------------------------------------------------- /src/components/article/basic/toggle-article-vote.js: -------------------------------------------------------------------------------- 1 | import ApolloProvider from '@/plugins/essential/apollo-provider'; 2 | import gql from '@/plugins/essential/graphql-tag'; 3 | 4 | const mutation = gql` 5 | mutation ToggleArticleVote($pk: ID!){ 6 | toggleArticleVote(pk:$pk){ 7 | state 8 | } 9 | } 10 | `; 11 | 12 | const toggleArticleVote = pk => ApolloProvider.defaultClient.mutate({ 13 | mutation, 14 | variables: { pk }, 15 | }); 16 | 17 | export default toggleArticleVote; 18 | -------------------------------------------------------------------------------- /src/components/article/basic/update-record.js: -------------------------------------------------------------------------------- 1 | import ApolloProvider from '@/plugins/essential/apollo-provider'; 2 | import gql from '@/plugins/essential/graphql-tag'; 3 | 4 | const mutate = gql` 5 | mutation UpdateArticleRecord($pk: ID!){ 6 | updateArticleRecord(pk:$pk){ 7 | state 8 | } 9 | } 10 | `; 11 | 12 | const updateArticleRecord = pk => ApolloProvider.defaultClient.mutate({ 13 | mutation: mutate, 14 | variables: { pk }, 15 | }); 16 | 17 | export default updateArticleRecord; 18 | -------------------------------------------------------------------------------- /src/components/article/home/create.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 85 | -------------------------------------------------------------------------------- /src/components/article/home/tile.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 | 96 | 97 | 102 | -------------------------------------------------------------------------------- /src/components/article/list/app.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 50 | 77 | -------------------------------------------------------------------------------- /src/components/article/list/list.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 65 | 82 | 83 | 104 | -------------------------------------------------------------------------------- /src/components/article/user/create.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 76 | -------------------------------------------------------------------------------- /src/components/article/user/tile.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 58 | 101 | 102 | 107 | -------------------------------------------------------------------------------- /src/components/async/code-mirror/loading-wrapper.vue: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/components/async/code-mirror/settings.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 93 | -------------------------------------------------------------------------------- /src/components/async/mixrend/app.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 36 | 37 | 45 | -------------------------------------------------------------------------------- /src/components/async/mixrend/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | import LoadingSpinner from '@/components/utils/loading-spinner'; 4 | 5 | const AsyncMixrendComponent = () => ({ 6 | component: import('./app'), 7 | loading: null, 8 | error: null, 9 | }); 10 | 11 | const AsyncMixrendComponentWithLoadingSpinner = () => ({ 12 | component: import('./app'), 13 | loading: LoadingSpinner, 14 | error: null, 15 | delay: 0, 16 | }); 17 | 18 | export { AsyncMixrendComponent, AsyncMixrendComponentWithLoadingSpinner }; 19 | -------------------------------------------------------------------------------- /src/components/chart/bar.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /src/components/chart/doughnut.vue: -------------------------------------------------------------------------------- 1 | 2 | 22 | -------------------------------------------------------------------------------- /src/components/chart/line.vue: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /src/components/comments/app.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 28 | 78 | -------------------------------------------------------------------------------- /src/components/comments/comments.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 86 | -------------------------------------------------------------------------------- /src/components/comments/editor.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 44 | 99 | -------------------------------------------------------------------------------- /src/components/comments/toggle-reply-vote.js: -------------------------------------------------------------------------------- 1 | import ApolloProvider from '@/plugins/essential/apollo-provider'; 2 | import gql from '@/plugins/essential/graphql-tag'; 3 | 4 | const mutation = gql` 5 | mutation ToggleReplyVote($pk: ID!){ 6 | toggleReplyVote(pk:$pk){ 7 | state 8 | } 9 | } 10 | `; 11 | 12 | const toggleReplyVote = pk => ApolloProvider.defaultClient.mutate({ 13 | mutation, 14 | variables: { pk }, 15 | }); 16 | 17 | export default toggleReplyVote; 18 | -------------------------------------------------------------------------------- /src/components/comments/update.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 66 | 138 | -------------------------------------------------------------------------------- /src/components/contest/detail/clarification.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 107 | -------------------------------------------------------------------------------- /src/components/contest/detail/problem.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 119 | 120 | 130 | -------------------------------------------------------------------------------- /src/components/contest/detail/submit.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 110 | -------------------------------------------------------------------------------- /src/components/contest/list/app.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 | 139 | -------------------------------------------------------------------------------- /src/components/contest/list/list.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 | 108 | -------------------------------------------------------------------------------- /src/components/contest/review/app.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 50 | 69 | -------------------------------------------------------------------------------- /src/components/contest/review/dialog.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 85 | -------------------------------------------------------------------------------- /src/components/contest/review/team-tile.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | 68 | 116 | -------------------------------------------------------------------------------- /src/components/contest/review/update-team.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 106 | -------------------------------------------------------------------------------- /src/components/contest/submission/summary.vue: -------------------------------------------------------------------------------- 1 | 94 | 95 | 96 | 144 | -------------------------------------------------------------------------------- /src/components/contest/utils.js: -------------------------------------------------------------------------------- 1 | import Vue from '@/plugins/essential/vue'; 2 | 3 | const getRunningStatus = (st, ed) => { 4 | const cur = Vue.moment(); 5 | const stTime = Vue.moment(st); 6 | const edTime = Vue.moment(ed); 7 | if (cur < stTime) { 8 | return 'Pending'; 9 | } 10 | if (cur < edTime) { 11 | return 'Running'; 12 | } 13 | if (cur >= edTime) { 14 | return 'Finished'; 15 | } 16 | return 'Unknown'; 17 | }; 18 | 19 | const getMinutesBetweenTwo = (st, ed) => Math.round((new Date(ed) - new Date(st)) / 1000 / 60); 20 | 21 | const getTimeLength = (st, ed) => { 22 | const len = getMinutesBetweenTwo(st, ed); 23 | const hour = String(Math.round(len / 60)); 24 | let minute = String(len % 60); 25 | if (minute.length === 1) { 26 | minute = `0${minute}`; 27 | } 28 | return `${hour}:${minute}`; 29 | }; 30 | 31 | 32 | export { getRunningStatus, getTimeLength, getMinutesBetweenTwo }; 33 | -------------------------------------------------------------------------------- /src/components/footer/app.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 60 | -------------------------------------------------------------------------------- /src/components/global/404.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /src/components/home/announcement.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 128 | -------------------------------------------------------------------------------- /src/components/home/blog.vue: -------------------------------------------------------------------------------- 1 | 86 | 87 | 126 | -------------------------------------------------------------------------------- /src/components/home/curtain.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 59 | 104 | 105 | 116 | -------------------------------------------------------------------------------- /src/components/home/develop.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 43 | 44 | 49 | -------------------------------------------------------------------------------- /src/components/home/guide.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/src/components/home/guide.vue -------------------------------------------------------------------------------- /src/components/home/home.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /src/components/home/honor.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 44 | 45 | 69 | -------------------------------------------------------------------------------- /src/components/home/recently.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 37 | 54 | 55 | 60 | -------------------------------------------------------------------------------- /src/components/language/utils/select.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 61 | -------------------------------------------------------------------------------- /src/components/navigation/app.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 46 | -------------------------------------------------------------------------------- /src/components/navigation/drawer.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 48 | 116 | -------------------------------------------------------------------------------- /src/components/navigation/toolbar.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 42 | 80 | -------------------------------------------------------------------------------- /src/components/navigation/user-menu-mobile.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | 56 | -------------------------------------------------------------------------------- /src/components/navigation/user-menu.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 63 | 94 | -------------------------------------------------------------------------------- /src/components/problem/detail/app.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 97 | -------------------------------------------------------------------------------- /src/components/problem/detail/description.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 39 | -------------------------------------------------------------------------------- /src/components/problem/detail/editor.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 79 | -------------------------------------------------------------------------------- /src/components/problem/detail/preview.vue: -------------------------------------------------------------------------------- 1 | 68 | 69 | 85 | -------------------------------------------------------------------------------- /src/components/problem/list/app.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 111 | -------------------------------------------------------------------------------- /src/components/problem/list/list.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 82 | -------------------------------------------------------------------------------- /src/components/problem/utils/auto-complete.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 78 | -------------------------------------------------------------------------------- /src/components/problem/utils/fetch.js: -------------------------------------------------------------------------------- 1 | import Apollo from '@/plugins/essential/apollo-provider'; 2 | 3 | 4 | const fetchProblemData = ({ slug, gql }) => new Promise((resolve, reject) => { 5 | Apollo.defaultClient.query({ 6 | query: gql, 7 | variables: { 8 | slug, 9 | }, 10 | }) 11 | .then(response => resolve(response)) 12 | .catch(error => reject(error)); 13 | }); 14 | 15 | 16 | export default fetchProblemData; 17 | -------------------------------------------------------------------------------- /src/components/problem/utils/preview.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 86 | -------------------------------------------------------------------------------- /src/components/signin/login.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 99 | -------------------------------------------------------------------------------- /src/components/signin/signout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 18 | -------------------------------------------------------------------------------- /src/components/snackbar/app.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /src/components/status/detail/code.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 33 | 63 | -------------------------------------------------------------------------------- /src/components/status/detail/connection.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import { getWebSocketUri } from '@/utils'; 3 | import store from '@/store'; 4 | 5 | const establishWebSocketConnection = pk => new Promise( 6 | (resolve, reject) => { 7 | const token = store.getters['user/token']; 8 | const ws = new WebSocket(`${getWebSocketUri()}/status/${String(pk)}/?${token}`); 9 | ws.onerror = error => reject(error); 10 | ws.onopen = resolve(ws); 11 | }, 12 | ); 13 | 14 | const closeWebSocketConnection = ws => new Promise( 15 | (resolve) => { 16 | if (ws) { 17 | ws.close(); 18 | } 19 | resolve(); 20 | }, 21 | ); 22 | 23 | const closeWebSocketConnectionSync = ws => { 24 | if( ws ){ 25 | ws.close(); 26 | } 27 | }; 28 | 29 | 30 | const waitingInitializing = ws => new Promise( 31 | (resolve) => { 32 | ws.onmessage = (data) => { 33 | ws.onmessage = null; 34 | resolve(data); 35 | }; 36 | }, 37 | ); 38 | 39 | export { establishWebSocketConnection, closeWebSocketConnection, waitingInitializing, closeWebSocketConnectionSync }; 40 | -------------------------------------------------------------------------------- /src/components/status/detail/progress.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 67 | -------------------------------------------------------------------------------- /src/components/status/detail/summary.vue: -------------------------------------------------------------------------------- 1 | 102 | 103 | 104 | 152 | -------------------------------------------------------------------------------- /src/components/status/list/app.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 56 | 106 | -------------------------------------------------------------------------------- /src/components/user/detail/app.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 46 | 74 | -------------------------------------------------------------------------------- /src/components/user/detail/layout.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 47 | -------------------------------------------------------------------------------- /src/components/user/detail/solved.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 45 | 55 | -------------------------------------------------------------------------------- /src/components/user/list/app.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 84 | -------------------------------------------------------------------------------- /src/components/user/list/list.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 52 | 101 | -------------------------------------------------------------------------------- /src/components/user/utils/auto-complete.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 121 | -------------------------------------------------------------------------------- /src/components/utils/code-mirror.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 87 | -------------------------------------------------------------------------------- /src/components/utils/error-spinner.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 33 | -------------------------------------------------------------------------------- /src/components/utils/loading-spinner.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 48 | -------------------------------------------------------------------------------- /src/components/utils/random-color.js: -------------------------------------------------------------------------------- 1 | 2 | const ColorList = [ 3 | 'red', 'pink', 'purple', 'deep-purple', 'indigo', 'blue', 'light-blue', 'cyan', 'teal', 'green', 'yellow', 'amber', 'orange', 'brown', 4 | ]; 5 | 6 | const getRandomColor = () => { 7 | const sz = ColorList.length; 8 | return ColorList[Math.floor(Math.random() * sz)]; 9 | }; 10 | 11 | export default getRandomColor; 12 | -------------------------------------------------------------------------------- /src/components/utils/search-bar.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 41 | -------------------------------------------------------------------------------- /src/components/verdict/utils/select.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 50 | -------------------------------------------------------------------------------- /src/docs/develop.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | [Lutece](https://github.com/lutece-awesome) is a fast iterative developing Online Judge platform, the Dev team still bring many amazing features on it, as open source project, you can also join the building of this 👏awesome project, this simple doc would guide you: 3 | - Architecture introduction 4 | - How to run dev server in localhost 5 | - Dev environment 6 | - How to submit PR to Lutece? 😎 7 | ## Part 1 - Architecture introduction 8 | There are three mainly parts of Lutece: 9 | * [Frontend](https://github.com/lutece-awesome/lutece-frontend)(Interact with user) 10 | + [Vue.js](https://vuejs.org/) as Javascript framework. 11 | + [Vuetify](https://vuetifyjs.com/en/) as UI framework. 12 | * [Backend](https://github.com/lutece-awesome/lutece-backend)(Business logic and database processing) 13 | + [Django](https://www.djangoproject.com/): Server framework with magic Python. 14 | + [Celery](http://www.celeryproject.org/): Distributed task queue framework. 15 | * [JudgeCore](https://github.com/lutece-awesome/osiris)(User submission judging) 16 | + [Docker](https://www.docker.com/): Sandbox solution. 17 | + [cgroups](https://en.wikipedia.org/wiki/Cgroups): (Still WIP)Runtime resources limitation. 18 | 19 | Lutece use the [GraphQL](https://graphql.org/) query language to process the data exchange between the server and client, for this, the client use [VueApollo](https://vue-apollo.netlify.com/) framework and server use [Graphene](https://graphene-python.org/) framework.Please attentain that this only list the most important frameworks using in Lutece, but still others. 20 | ## Part 2 - How to run dev server in localhost 21 | To easy start, we do not consider run local judge osiris core. 22 | 23 | Run dev frontend server in localhost is quite easy, please follow the [README](https://github.com/lutece-awesome/lutece-frontend/blob/master/README.md). 24 | 25 | Run dev backend server is more complexity, please follow these steps: 26 | + pip3 install -r requirements/requirements.txt 27 | + cp Lutece/config.py.tempalte Lutece/config.py 28 | + [make migrations](https://github.com/lutece-awesome/lutece-backend/blob/master/.travis.yml#L23) 29 | + [migrate](https://github.com/lutece-awesome/lutece-backend/blob/master/.travis.yml#L24) 30 | + python3 manage.py runserver 31 | ## Part 3 - Dev environment 32 | Frontend: 33 | + Use [VSCode](https://code.visualstudio.com/). 34 | 35 | Backend: 36 | + Use [Pycharm](https://www.jetbrains.com/pycharm/) professional version(if you are student, you can apply [student license](https://www.jetbrains.com/student/)). 37 | ## Part 4 - How to submit PR 38 | Please follow the github PR [guide](https://help.github.com/articles/creating-a-pull-request/). 39 | 40 | And for all PRs, please specify [lutece-awesome/review](https://github.com/orgs/lutece-awesome/teams/review/members) as [reviewers](https://help.github.com/articles/requesting-a-pull-request-review/). 41 | 42 | As osiris core planning to entire refactor, so any PRs to osiris would not be accepted for now. -------------------------------------------------------------------------------- /src/graphql/article/create.gql: -------------------------------------------------------------------------------- 1 | mutation CreateArticleGQL($title: String!, $content: String!){ 2 | createArticle(title: $title, content: $content) { 3 | state 4 | } 5 | } -------------------------------------------------------------------------------- /src/graphql/article/detail.gql: -------------------------------------------------------------------------------- 1 | query ArticleGQL( $slug: String! ){ 2 | article( slug: $slug ){ 3 | title 4 | content 5 | view 6 | vote 7 | createTime 8 | user{ 9 | username 10 | displayName 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/graphql/article/discussion.gql: -------------------------------------------------------------------------------- 1 | query ComemntsGQL( $page: Int! , $slug: String! , $time: Int ){ 2 | articleDiscussionList( page: $page , slug: $slug , time: $time ){ 3 | maxPage 4 | discussionList{ 5 | user{ 6 | username 7 | displayName 8 | gravataremail 9 | } 10 | content 11 | submitTime 12 | vote 13 | pk 14 | attitude 15 | reply{ 16 | pk 17 | submitTime 18 | content 19 | user{ 20 | username 21 | displayName 22 | gravataremail 23 | } 24 | vote 25 | attitude 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/graphql/article/list.gql: -------------------------------------------------------------------------------- 1 | query ArticleListGQL($page: Int!) { 2 | articleList(page: $page) { 3 | maxPage 4 | ArticleList { 5 | title 6 | vote 7 | view 8 | user{ 9 | displayName 10 | username 11 | gravataremail 12 | } 13 | slug 14 | createTime 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/graphql/home/home-article-list.gql: -------------------------------------------------------------------------------- 1 | query HomeArticleListGQL($page: Int!, $filter: String) { 2 | homeArticleList(page: $page, filter: $filter) { 3 | maxPage 4 | homeArticleList { 5 | title 6 | slug 7 | preview 8 | author { 9 | username 10 | attachInfo{ 11 | gravatar 12 | } 13 | } 14 | record { 15 | count 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/graphql/home/topuser.gql: -------------------------------------------------------------------------------- 1 | query TopUserGQL($number: Int!) { 2 | topUser(number: $number) { 3 | username 4 | displayName 5 | gravataremail 6 | solved 7 | tried 8 | rank 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/graphql/home/user-article-list.gql: -------------------------------------------------------------------------------- 1 | query UserArticleListGQL($page: Int!, $filter: String) { 2 | userArticleList(page: $page, filter: $filter) { 3 | maxPage 4 | userArticleList { 5 | pk 6 | title 7 | author { 8 | username 9 | attachInfo{ 10 | gravatar 11 | } 12 | } 13 | record { 14 | count 15 | } 16 | vote 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/graphql/language/languagelist.gql: -------------------------------------------------------------------------------- 1 | query LanguageList{ 2 | allLanguage 3 | } -------------------------------------------------------------------------------- /src/graphql/problem/detail.gql: -------------------------------------------------------------------------------- 1 | query ProblemDetailGQL($slug: String!) { 2 | problem(slug: $slug) { 3 | pk 4 | title 5 | content 6 | standardInput 7 | standardOutput 8 | constraints 9 | resources 10 | note 11 | limitation{ 12 | timeLimit 13 | memoryLimit 14 | outputLimit 15 | cpuLimit 16 | } 17 | samples { 18 | sampleList{ 19 | inputContent 20 | outputContent 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/graphql/problem/edit-detail.gql: -------------------------------------------------------------------------------- 1 | query ProblemEditDetailGQL($slug: String!) { 2 | problem(slug: $slug) { 3 | pk 4 | title 5 | content 6 | standardInput 7 | standardOutput 8 | constraints 9 | resources 10 | note 11 | limitation{ 12 | timeLimit 13 | memoryLimit 14 | outputLimit 15 | cpuLimit 16 | } 17 | disable 18 | samples { 19 | sampleList{ 20 | inputContent 21 | outputContent 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/graphql/problem/edit.gql: -------------------------------------------------------------------------------- 1 | mutation UpdateProblemGQL($title: String!, $content: String!, $note: String!, $timeLimit: Int!, $memoryLimit: Int!, $constraints: String!, $resources: String!, $standardInput: String! , $standardOutput: String! , $slug: String! , $samples: String! , $disable: Boolean! , $outputLimit: Int! , $cpuLimit: Int! ) { 2 | updateProblem( 3 | title: $title, 4 | content: $content, 5 | note: $note, 6 | timeLimit: $timeLimit, 7 | memoryLimit: $memoryLimit, 8 | outputLimit: $outputLimit, 9 | cpuLimit: $cpuLimit, 10 | constraints: $constraints, 11 | resources: $resources, 12 | standardInput: $standardInput, 13 | standardOutput: $standardOutput, 14 | slug: $slug, 15 | samples: $samples, 16 | disable: $disable 17 | ) { 18 | slug 19 | } 20 | } -------------------------------------------------------------------------------- /src/graphql/problem/list.gql: -------------------------------------------------------------------------------- 1 | query ProblemListGQL($page: Int!, $filter: String) { 2 | problemList(page: $page, filter: $filter) { 3 | maxPage 4 | problemList { 5 | pk 6 | title 7 | submit 8 | accept 9 | slug 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/graphql/problem/search.gql: -------------------------------------------------------------------------------- 1 | query ProblemSearchGQL($filter: String) { 2 | problemSearch(filter: $filter) { 3 | problemList { 4 | pk 5 | title 6 | slug 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/graphql/signin/register.gql: -------------------------------------------------------------------------------- 1 | mutation RegisterGQL( 2 | $username: String! 3 | $password: String! 4 | $email: String! 5 | $about: String! 6 | $school: String 7 | $company: String 8 | $location: String 9 | $codeforces: String 10 | $atcoder: String 11 | $studentid: String 12 | $gender: Boolean 13 | ) { 14 | userRegister( 15 | username: $username 16 | password: $password 17 | email: $email 18 | about: $about 19 | school: $school 20 | company: $company 21 | location: $location 22 | codeforces: $codeforces 23 | atcoder: $atcoder 24 | studentid: $studentid 25 | gender: $gender 26 | ) { 27 | token 28 | payload 29 | permission 30 | user{ 31 | username 32 | attachInfo{ 33 | gravatar 34 | school 35 | company 36 | location 37 | about 38 | codeforces 39 | atcoder 40 | studentid 41 | gender 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/graphql/signin/token.gql: -------------------------------------------------------------------------------- 1 | mutation UserLogin($username: String!, $password: String!) { 2 | userLogin(username: $username, password: $password) { 3 | token 4 | payload 5 | permission 6 | user{ 7 | username 8 | attachInfo{ 9 | gravatar 10 | school 11 | company 12 | location 13 | about 14 | codeforces 15 | atcoder 16 | studentid 17 | gender 18 | } 19 | } 20 | } 21 | } 22 | 23 | mutation VerifyToken($token: String!) { 24 | verifyToken(token: $token) { 25 | payload 26 | } 27 | } 28 | 29 | mutation RefreshToken($token: String!) { 30 | userTokenRefresh(token: $token) { 31 | token 32 | payload 33 | permission 34 | user{ 35 | username 36 | attachInfo{ 37 | gravatar 38 | school 39 | company 40 | location 41 | about 42 | codeforces 43 | atcoder 44 | studentid 45 | gender 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/graphql/submission/list.gql: -------------------------------------------------------------------------------- 1 | query SubmissionListGQL( 2 | $page: Int! 3 | $pk: ID 4 | $user: String 5 | $problem: String 6 | $judgeStatus: String 7 | $language: String 8 | ) { 9 | submissionList( 10 | page: $page 11 | pk: $pk 12 | user: $user 13 | problem: $problem 14 | judgeStatus: $judgeStatus 15 | language: $language 16 | ) { 17 | maxPage 18 | submissionList { 19 | pk 20 | failedCase 21 | createTime 22 | user { 23 | username 24 | attachInfo{ 25 | gravatar 26 | } 27 | } 28 | problem { 29 | title 30 | slug 31 | } 32 | result{ 33 | status 34 | color 35 | } 36 | language 37 | attachInfo{ 38 | timeCost 39 | memoryCost 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/graphql/submission/submit.gql: -------------------------------------------------------------------------------- 1 | mutation SubmitSubmission($code: String!, $problemSlug: String! , $language: String!) { 2 | submitSubmission(code: $code, problemSlug: $problemSlug , language: $language) { 3 | pk 4 | } 5 | } -------------------------------------------------------------------------------- /src/graphql/user/list.gql: -------------------------------------------------------------------------------- 1 | query UserListGQL($page: Int!, $filter: String) { 2 | userList(page: $page, filter: $filter) { 3 | maxPage 4 | userList { 5 | username 6 | attachInfo{ 7 | gravatar 8 | } 9 | solved 10 | tried 11 | statistics{ 12 | ratio 13 | } 14 | rank{ 15 | position 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/graphql/user/profile.gql: -------------------------------------------------------------------------------- 1 | query ProfileGQL($username: String!) { 2 | user( username: $username){ 3 | username 4 | attachInfo{ 5 | school 6 | company 7 | location 8 | about 9 | gravatar 10 | codeforces 11 | atcoder 12 | studentid 13 | gender 14 | } 15 | solved 16 | tried 17 | joinedDate 18 | lastLoginDate 19 | rank{ 20 | position 21 | count 22 | } 23 | statistics{ 24 | ac 25 | tle 26 | ce 27 | wa 28 | re 29 | ole 30 | mle 31 | solve{ 32 | pk 33 | slug 34 | status 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/graphql/user/search.gql: -------------------------------------------------------------------------------- 1 | query UserSearchGQL($filter: String) { 2 | userSearch(filter: $filter) { 3 | userList { 4 | username 5 | attachInfo{ 6 | gravatar 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/graphql/user/settings.gql: -------------------------------------------------------------------------------- 1 | mutation UserInfoUpdateGQL( 2 | $about: String!, 3 | $company: String!, 4 | $school: String!, 5 | $location: String!, 6 | $codeforces: String!, 7 | $atcoder: String!, 8 | $studentid: String!, 9 | $gender: Boolean! 10 | ){ 11 | userAttachInfoUpdate( 12 | company: $company, 13 | about: $about, 14 | school: $school, 15 | location: $location, 16 | codeforces: $codeforces, 17 | atcoder: $atcoder, 18 | studentid: $studentid, 19 | gender: $gender 20 | ){ 21 | state 22 | } 23 | } -------------------------------------------------------------------------------- /src/graphql/utils/uploadimage.gql: -------------------------------------------------------------------------------- 1 | mutation UploadImageGQL($file: Upload!){ 2 | UploadImage(file:$file){ 3 | path 4 | } 5 | } -------------------------------------------------------------------------------- /src/graphql/votediscussion/vote.gql: -------------------------------------------------------------------------------- 1 | mutation VoteDiscussionGQL( 2 | $pk: ID! 3 | $attitude: Boolean! 4 | ) { 5 | UpdateDiscussionVote( 6 | pk: $pk 7 | attitude: $attitude 8 | ) { 9 | vote 10 | result 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import '@babel/polyfill'; 2 | 3 | import loadingPlugins from './plugins/index'; 4 | import Vue from './plugins/essential/vue'; 5 | import router from './plugins/essential/router'; 6 | import store from './plugins/essential/store'; 7 | import apolloProvider from './plugins/essential/apollo-provider'; 8 | import App from './App'; 9 | 10 | loadingPlugins.loadingEssential().then(() => { 11 | loadingPlugins.loadingExternal(); 12 | new Vue({ 13 | router, 14 | store, 15 | apolloProvider, 16 | render: h => h(App), 17 | }).$mount('#app'); 18 | }); 19 | -------------------------------------------------------------------------------- /src/modules/code-mirror/keymap.js: -------------------------------------------------------------------------------- 1 | const keymapPlugins = [ 2 | { 3 | name: 'default', 4 | import: () => Promise.resolve(), 5 | }, 6 | { 7 | name: 'sublime', 8 | import: () => import('codemirror/keymap/sublime'), 9 | }, 10 | { 11 | name: 'emacs', 12 | import: () => import('codemirror/keymap/emacs'), 13 | }, 14 | { 15 | name: 'vim', 16 | import: () => import('codemirror/keymap/vim'), 17 | }, 18 | ]; 19 | 20 | export default { 21 | all: () => keymapPlugins, 22 | valueOf: result => keymapPlugins.find(element => element.name === result), 23 | first: () => keymapPlugins[0], 24 | }; 25 | -------------------------------------------------------------------------------- /src/modules/language/main.js: -------------------------------------------------------------------------------- 1 | const LanguageList = [ 2 | { 3 | full: 'GNU G++', 4 | info: 'GNU G++17', 5 | codeMirror: 'text/x-c++src', 6 | codeMirrorImport: () => import('codemirror/mode/clike/clike'), 7 | }, 8 | { 9 | full: 'GNU GCC', 10 | info: 'GNU GCC 7.3', 11 | codeMirror: 'text/x-csrc', 12 | codeMirrorImport: () => import('codemirror/mode/clike/clike'), 13 | }, 14 | // { 15 | // full: 'Clang', 16 | // info: 'Clang 6.0.0', 17 | // codeMirror: 'text/x-c++src', 18 | // }, 19 | { 20 | full: 'Python', 21 | info: 'Python 3.6.5', 22 | codeMirror: 'text/x-python', 23 | codeMirrorImport: () => import('codemirror/mode/python/python'), 24 | }, 25 | { 26 | full: 'Java', 27 | info: 'Java 10', 28 | codeMirror: 'text/x-java', 29 | // Java using clike 30 | codeMirrorImport: () => import('codemirror/mode/clike/clike'), 31 | }, 32 | { 33 | full: 'Go', 34 | info: 'Go 1.10.2', 35 | codeMirror: 'text/x-go', 36 | codeMirrorImport: () => import('codemirror/mode/go/go'), 37 | }, 38 | { 39 | full: 'Ruby', 40 | info: 'Ruby 2.5.1', 41 | codeMirror: 'text/x-ruby', 42 | codeMirrorImport: () => import('codemirror/mode/ruby/ruby'), 43 | }, 44 | { 45 | full: 'Rust', 46 | info: 'Rust 1.26.1', 47 | codeMirror: 'text/x-rustsrc', 48 | codeMirrorImport: () => import('codemirror/mode/rust/rust'), 49 | }, 50 | ]; 51 | 52 | const valueOf = result => LanguageList.find(element => element.full === result 53 | || element.info === result); 54 | 55 | const Language = { 56 | valueOf: result => valueOf(result), 57 | all: () => LanguageList, 58 | first: () => LanguageList[0], 59 | }; 60 | 61 | export default Language; 62 | -------------------------------------------------------------------------------- /src/modules/verdict/main.js: -------------------------------------------------------------------------------- 1 | const VerdictList = [ 2 | { 3 | full: 'Pending', 4 | short: 'pd', 5 | textColor: 'info', 6 | }, 7 | { 8 | full: 'Preparing', 9 | short: 'pr', 10 | textColor: 'info', 11 | }, 12 | { 13 | full: 'Accepted', 14 | short: 'ac', 15 | backgroundColor: 'rgba(0, 255, 127, 0.2)', 16 | borderColor: 'rgba(0, 255, 127, 1)', 17 | textColor: 'success', 18 | }, 19 | { 20 | full: 'Running', 21 | short: 'rn', 22 | textColor: 'info', 23 | }, 24 | { 25 | full: 'Compile Error', 26 | short: 'ce', 27 | backgroundColor: 'rgba(54, 162, 235, 0.2)', 28 | borderColor: 'rgba(54, 162, 235, 1)', 29 | textColor: 'warning', 30 | }, 31 | { 32 | full: 'Wrong Answer', 33 | short: 'wa', 34 | backgroundColor: 'rgba(255, 99, 132, 0.2)', 35 | borderColor: 'rgba(255, 99, 132, 1)', 36 | textColor: 'error', 37 | }, 38 | { 39 | full: 'Runtime Error', 40 | short: 're', 41 | backgroundColor: 'rgba(255, 206, 86, 0.2)', 42 | borderColor: 'rgba(255, 206, 86, 1)', 43 | textColor: 'error', 44 | }, 45 | { 46 | full: 'Time Limit Exceeded', 47 | short: 'tle', 48 | backgroundColor: 'rgba(75, 192, 192, 0.2)', 49 | borderColor: 'rgba(75, 192, 192, 1)', 50 | textColor: 'error', 51 | }, 52 | { 53 | full: 'Output Limit Exceeded', 54 | short: 'ole', 55 | backgroundColor: 'rgba(153, 102, 255, 0.2)', 56 | borderColor: 'rgba(153, 102, 255, 1)', 57 | textColor: 'error', 58 | }, 59 | { 60 | full: 'Memory Limit Exceeded', 61 | short: 'mle', 62 | backgroundColor: 'rgba(255, 159, 64, 0.2)', 63 | borderColor: 'rgba(255, 159, 64, 1)', 64 | textColor: 'error', 65 | }, 66 | { 67 | full: 'Judger Error', 68 | short: 'je', 69 | textColor: 'warning', 70 | }, 71 | ]; 72 | 73 | const valueOf = result => VerdictList.find(element => element.full === result 74 | || element.short === result); 75 | 76 | const Verdict = { 77 | valueOf: result => valueOf(result), 78 | all: () => VerdictList, 79 | pd: valueOf('pd'), 80 | pr: valueOf('pr'), 81 | ac: valueOf('ac'), 82 | rn: valueOf('rn'), 83 | ce: valueOf('ce'), 84 | wa: valueOf('wa'), 85 | re: valueOf('re'), 86 | tle: valueOf('tle'), 87 | ole: valueOf('ole'), 88 | mle: valueOf('mle'), 89 | je: valueOf('je'), 90 | }; 91 | 92 | 93 | export default Verdict; 94 | -------------------------------------------------------------------------------- /src/npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/usr/bin/node', 3 | 1 verbose cli '/usr/bin/npm', 4 | 1 verbose cli 'install', 5 | 1 verbose cli '--save', 6 | 1 verbose cli 'plugins/external/markdown-it-vue' ] 7 | 2 info using npm@3.5.2 8 | 3 info using node@v8.10.0 9 | 4 silly loadCurrentTree Starting 10 | 5 silly install loadCurrentTree 11 | 6 silly install readLocalPackageData 12 | 7 silly fetchPackageMetaData plugins/external/markdown-it-vue 13 | 8 silly fetchOtherPackageData plugins/external/markdown-it-vue 14 | 9 silly cache add args [ 'plugins/external/markdown-it-vue', null ] 15 | 10 verbose cache add spec plugins/external/markdown-it-vue 16 | 11 silly cache add parsed spec Result { 17 | 11 silly cache add raw: 'plugins/external/markdown-it-vue', 18 | 11 silly cache add scope: null, 19 | 11 silly cache add name: null, 20 | 11 silly cache add rawSpec: 'plugins/external/markdown-it-vue', 21 | 11 silly cache add spec: '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/external/markdown-it-vue', 22 | 11 silly cache add type: 'local' } 23 | 12 error addLocal Could not install /home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/external/markdown-it-vue 24 | 13 silly fetchPackageMetaData Error: ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/external/markdown-it-vue' 25 | 13 silly fetchPackageMetaData error for plugins/external/markdown-it-vue { Error: ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/external/markdown-it-vue' 26 | 13 silly fetchPackageMetaData errno: -2, 27 | 13 silly fetchPackageMetaData code: 'ENOENT', 28 | 13 silly fetchPackageMetaData syscall: 'open', 29 | 13 silly fetchPackageMetaData path: '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/external/markdown-it-vue' } 30 | 14 silly rollbackFailedOptional Starting 31 | 15 silly rollbackFailedOptional Finishing 32 | 16 silly runTopLevelLifecycles Starting 33 | 17 silly runTopLevelLifecycles Finishing 34 | 18 silly install printInstalled 35 | 19 verbose stack Error: ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/external/markdown-it-vue' 36 | 20 verbose cwd /home/hexisyztem/lutece/frontend/lutece-frontend/src 37 | 21 error Linux 5.0.0-37-generic 38 | 22 error argv "/usr/bin/node" "/usr/bin/npm" "install" "--save" "plugins/external/markdown-it-vue" 39 | 23 error node v8.10.0 40 | 24 error npm v3.5.2 41 | 25 error path /home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/external/markdown-it-vue 42 | 26 error code ENOENT 43 | 27 error errno -2 44 | 28 error syscall open 45 | 29 error enoent ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/external/markdown-it-vue' 46 | 30 error enoent ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/external/markdown-it-vue' 47 | 30 error enoent This is most likely not a problem with npm itself 48 | 30 error enoent and is related to npm not being able to find a file. 49 | 31 verbose exit [ -2, true ] 50 | -------------------------------------------------------------------------------- /src/plugins/essential/apollo-provider.js: -------------------------------------------------------------------------------- 1 | import Vue from './vue'; 2 | import { ApolloClient } from 'apollo-client'; 3 | import { createUploadLink } from 'apollo-upload-client'; 4 | import { InMemoryCache } from 'apollo-cache-inmemory'; 5 | import { setContext } from 'apollo-link-context'; 6 | import VueApollo from 'vue-apollo'; 7 | import fetch from 'unfetch'; 8 | import { getGraphQLUri } from '@/utils'; 9 | 10 | /* 11 | Only used to support native Apollo component to fetch data. 12 | */ 13 | 14 | const httpLink = createUploadLink({ 15 | // You should use an absolute URL here 16 | uri: getGraphQLUri(), 17 | credentials: 'same-origin', 18 | fetch, 19 | }); 20 | 21 | const httpLinkAuth = setContext((_, { headers }) => { 22 | // get the authentication token from localstorage if it exists 23 | const token = localStorage.getItem('USER_TOKEN'); 24 | // return the headers to the context so httpLink can read them 25 | if (token) { 26 | return { 27 | headers: { 28 | ...headers, 29 | Authorization: `JWT ${token}`, 30 | }, 31 | }; 32 | } 33 | return { headers }; 34 | }); 35 | 36 | const apolloClient = new ApolloClient({ 37 | link: httpLinkAuth.concat(httpLink), 38 | cache: new InMemoryCache(), 39 | connectToDevTools: true, 40 | }); 41 | 42 | Vue.use(VueApollo); 43 | 44 | const apolloProvider = new VueApollo({ 45 | defaultClient: apolloClient, 46 | }); 47 | 48 | export default apolloProvider; 49 | -------------------------------------------------------------------------------- /src/plugins/essential/auth.js: -------------------------------------------------------------------------------- 1 | import Store from './store'; 2 | 3 | export default Store.dispatch('user/refresh_token', true); 4 | -------------------------------------------------------------------------------- /src/plugins/essential/filters.js: -------------------------------------------------------------------------------- 1 | import Vue from './vue'; 2 | 3 | Vue.filter('nl2br', text => text.replace(/(?:\r\n|\r|\n)/g, '
')); 4 | -------------------------------------------------------------------------------- /src/plugins/essential/global-components.js: -------------------------------------------------------------------------------- 1 | import Vue from './vue'; 2 | import LoadingSpinner from '@/components/utils/loading-spinner'; 3 | import ErrorSpinner from '@/components/utils/error-spinner'; 4 | 5 | Vue.component('loading-spinner', LoadingSpinner); 6 | Vue.component('error-spinner', ErrorSpinner); 7 | -------------------------------------------------------------------------------- /src/plugins/essential/graphql-tag.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export default gql; 4 | -------------------------------------------------------------------------------- /src/plugins/essential/router.js: -------------------------------------------------------------------------------- 1 | import Router from '@/router/index'; 2 | 3 | export default Router; 4 | -------------------------------------------------------------------------------- /src/plugins/essential/store.js: -------------------------------------------------------------------------------- 1 | import store from '@/store/index'; 2 | 3 | export default store; 4 | -------------------------------------------------------------------------------- /src/plugins/essential/vue-line-clamp.js: -------------------------------------------------------------------------------- 1 | import LineClamp from 'vue-line-clamp'; 2 | import Vue from './vue'; 3 | 4 | Vue.use(LineClamp); 5 | -------------------------------------------------------------------------------- /src/plugins/essential/vue-moment.js: -------------------------------------------------------------------------------- 1 | import VueMoment from 'vue-moment'; 2 | import Vue from './vue'; 3 | 4 | Vue.use(VueMoment); 5 | -------------------------------------------------------------------------------- /src/plugins/essential/vue.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | Vue.config.productionTip = false; 4 | 5 | export default Vue; 6 | -------------------------------------------------------------------------------- /src/plugins/essential/vuetify.js: -------------------------------------------------------------------------------- 1 | import { 2 | Vuetify, // required 3 | VWindow, 4 | VItemGroup, 5 | VSlider, 6 | VTimePicker, 7 | VDatePicker, 8 | VDialog, 9 | VCheckbox, 10 | VChip, 11 | VAlert, 12 | VImg, 13 | VProgressCircular, 14 | VHover, 15 | VTooltip, 16 | VApp, // required 17 | VTextarea, 18 | VSubheader, 19 | VSwitch, 20 | VNavigationDrawer, 21 | VGrid, 22 | VToolbar, 23 | VList, 24 | VBtn, 25 | VAvatar, 26 | VCard, 27 | VMenu, 28 | VIcon, 29 | VAutocomplete, 30 | VDataTable, 31 | VPagination, 32 | VTabs, 33 | VSelect, 34 | VTextField, 35 | VForm, 36 | VDivider, 37 | VProgressLinear, 38 | VSnackbar, 39 | VDataIterator, 40 | transitions, 41 | } from 'vuetify'; 42 | import { Resize } from 'vuetify/es5/directives'; 43 | import colors from 'vuetify/es5/util/colors'; 44 | import DatetimePicker from 'vuetify-datetime-picker'; 45 | import Vue from './vue'; 46 | import '@/stylus/main.styl'; 47 | import '@mdi/font/css/materialdesignicons.css'; 48 | import 'vuetify-datetime-picker/src/stylus/main.styl'; 49 | 50 | Vue.use(DatetimePicker); 51 | 52 | Vue.use(Vuetify, { 53 | components: { 54 | VWindow, 55 | VItemGroup, 56 | VSlider, 57 | VTimePicker, 58 | VDatePicker, 59 | VDialog, 60 | VCheckbox, 61 | VChip, 62 | VAlert, 63 | VImg, 64 | VProgressCircular, 65 | VHover, 66 | VTooltip, 67 | VTextarea, 68 | VSubheader, 69 | VSwitch, 70 | VApp, 71 | VNavigationDrawer, 72 | VGrid, 73 | VToolbar, 74 | VList, 75 | VBtn, 76 | VAvatar, 77 | VCard, 78 | VMenu, 79 | VIcon, 80 | VAutocomplete, 81 | VDataTable, 82 | VPagination, 83 | VTabs, 84 | VSelect, 85 | VTextField, 86 | VForm, 87 | VDivider, 88 | VProgressLinear, 89 | VSnackbar, 90 | VDataIterator, 91 | transitions, 92 | }, 93 | directives: { 94 | Resize, 95 | }, 96 | iconfont: 'mdi', 97 | theme: { 98 | primary: colors.blue.darken1, 99 | secondary: colors.blue.darken2, 100 | accent: colors.pink.base, 101 | }, 102 | }); 103 | -------------------------------------------------------------------------------- /src/plugins/external/code-mirror.js: -------------------------------------------------------------------------------- 1 | // require component 2 | import { codemirror } from 'vue-codemirror'; 3 | 4 | // require styles 5 | import 'codemirror/lib/codemirror.css'; 6 | 7 | // scrollbar 8 | import 'codemirror/addon/scroll/simplescrollbars'; 9 | import 'codemirror/addon/scroll/simplescrollbars.css'; 10 | 11 | // auto refresh 12 | import 'codemirror/addon/display/autorefresh'; 13 | 14 | // active line 15 | import 'codemirror/addon/selection/active-line'; 16 | 17 | // match brackets 18 | import 'codemirror/addon/edit/matchbrackets'; 19 | 20 | // close brackets 21 | import 'codemirror/addon/edit/closebrackets'; 22 | 23 | export default codemirror; 24 | -------------------------------------------------------------------------------- /src/plugins/external/markdown-it-katex.js: -------------------------------------------------------------------------------- 1 | import MarkdownIt from 'markdown-it'; 2 | import TexMathPlugin from 'markdown-it-texmath'; 3 | import Katex from 'katex'; 4 | import 'katex/dist/katex.css'; 5 | 6 | TexMathPlugin.use(Katex); 7 | 8 | /** 9 | * For markdown it options, ref to https://github.com/markdown-it/markdown-it. 10 | * For security settings, ref to https://github.com/markdown-it/markdown-it/blob/master/docs/security.md. 11 | */ 12 | const StrictMarkdownParser = MarkdownIt({ 13 | html: false, // Enable HTML tags in source 14 | xhtmlOut: false, // Use '/' to close single tags (
). 15 | // This is only for full CommonMark compatibility. 16 | breaks: true, // Convert '\n' in paragraphs into
17 | // useful for external highlighters. 18 | linkify: true, // Autoconvert URL-like text to links 19 | typographer: true, 20 | quotes: '“”‘’', 21 | }) 22 | .use(TexMathPlugin); 23 | 24 | // Only used for admin 25 | const DangerousMarkdownParser = MarkdownIt({ 26 | html: true, // Enable HTML tags in source 27 | xhtmlOut: false, // Use '/' to close single tags (
). 28 | // This is only for full CommonMark compatibility. 29 | breaks: false, // Convert '\n' in paragraphs into
30 | // useful for external highlighters. 31 | linkify: false, // Autoconvert URL-like text to links 32 | typographer: true, 33 | quotes: '“”‘’', 34 | }) 35 | .use(TexMathPlugin); 36 | 37 | export { StrictMarkdownParser, DangerousMarkdownParser }; 38 | -------------------------------------------------------------------------------- /src/plugins/external/markdown-it-vue.js: -------------------------------------------------------------------------------- 1 | import MarkdownItVue from 'markdown-it-vue'; 2 | import Katex from 'katex'; 3 | import 'katex/dist/katex.css'; 4 | 5 | TexMathPlugin.use(Katex); 6 | 7 | /** 8 | * For markdown it options, ref to https://github.com/markdown-it/markdown-it. 9 | * For security settings, ref to https://github.com/markdown-it/markdown-it/blob/master/docs/security.md. 10 | */ 11 | const StrictMarkdownParser = MarkdownIt({ 12 | html: false, // Enable HTML tags in source 13 | xhtmlOut: false, // Use '/' to close single tags (
). 14 | // This is only for full CommonMark compatibility. 15 | breaks: false, // Convert '\n' in paragraphs into
16 | // useful for external highlighters. 17 | linkify: false, // Autoconvert URL-like text to links 18 | typographer: true, 19 | quotes: '“”‘’', 20 | }) 21 | .use(TexMathPlugin); 22 | 23 | // Only used for admin 24 | const DangerousMarkdownParser = MarkdownIt({ 25 | html: true, // Enable HTML tags in source 26 | xhtmlOut: false, // Use '/' to close single tags (
). 27 | // This is only for full CommonMark compatibility. 28 | breaks: false, // Convert '\n' in paragraphs into
29 | // useful for external highlighters. 30 | linkify: false, // Autoconvert URL-like text to links 31 | typographer: true, 32 | quotes: '“”‘’', 33 | }) 34 | .use(TexMathPlugin); 35 | 36 | export { StrictMarkdownParser, DangerousMarkdownParser }; 37 | -------------------------------------------------------------------------------- /src/plugins/index.js: -------------------------------------------------------------------------------- 1 | 2 | import authPromise from './essential/auth'; 3 | 4 | // essential plugins, which would block the initializing. 5 | const essentialPlugins = () => [ 6 | // Pre auth 7 | authPromise, 8 | // Vue.js as JS Framework 9 | import('./essential/vue'), 10 | // Vuetify UI Framework 11 | import('./essential/vuetify'), 12 | // Vue Router 13 | import('./essential/router'), 14 | // Apollo Provider 15 | import('./essential/apollo-provider'), 16 | // Global components 17 | import('./essential/global-components'), 18 | // Line clamp 19 | import('./essential/vue-line-clamp'), 20 | // Apollo related 21 | import('./essential/apollo-provider'), 22 | // Filter plugin, support nl2br 23 | import('./essential/filters'), 24 | // Vue moment 25 | import('./essential/vue-moment'), 26 | // Graphql Tag 27 | import('./essential/graphql-tag'), 28 | ]; 29 | 30 | const externalPlugins = () => [ 31 | // Markdown it katex plugin 32 | import('./external/markdown-it-katex'), 33 | // Code Mirror 34 | import('./external/code-mirror'), 35 | ]; 36 | 37 | export default { 38 | loadingEssential: () => Promise.all(essentialPlugins()), 39 | loadingExternal: () => Promise.all(externalPlugins()), 40 | }; 41 | -------------------------------------------------------------------------------- /src/plugins/npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ '/usr/bin/node', 3 | 1 verbose cli '/usr/bin/npm', 4 | 1 verbose cli 'install', 5 | 1 verbose cli '--save', 6 | 1 verbose cli 'plugins/external/markdown-it-vue' ] 7 | 2 info using npm@3.5.2 8 | 3 info using node@v8.10.0 9 | 4 silly loadCurrentTree Starting 10 | 5 silly install loadCurrentTree 11 | 6 silly install readLocalPackageData 12 | 7 silly fetchPackageMetaData plugins/external/markdown-it-vue 13 | 8 silly fetchOtherPackageData plugins/external/markdown-it-vue 14 | 9 silly cache add args [ 'plugins/external/markdown-it-vue', null ] 15 | 10 verbose cache add spec plugins/external/markdown-it-vue 16 | 11 silly cache add parsed spec Result { 17 | 11 silly cache add raw: 'plugins/external/markdown-it-vue', 18 | 11 silly cache add scope: null, 19 | 11 silly cache add name: null, 20 | 11 silly cache add rawSpec: 'plugins/external/markdown-it-vue', 21 | 11 silly cache add spec: '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/plugins/external/markdown-it-vue', 22 | 11 silly cache add type: 'local' } 23 | 12 error addLocal Could not install /home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/plugins/external/markdown-it-vue 24 | 13 silly fetchPackageMetaData Error: ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/plugins/external/markdown-it-vue' 25 | 13 silly fetchPackageMetaData error for plugins/external/markdown-it-vue { Error: ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/plugins/external/markdown-it-vue' 26 | 13 silly fetchPackageMetaData errno: -2, 27 | 13 silly fetchPackageMetaData code: 'ENOENT', 28 | 13 silly fetchPackageMetaData syscall: 'open', 29 | 13 silly fetchPackageMetaData path: '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/plugins/external/markdown-it-vue' } 30 | 14 silly rollbackFailedOptional Starting 31 | 15 silly rollbackFailedOptional Finishing 32 | 16 silly runTopLevelLifecycles Starting 33 | 17 silly runTopLevelLifecycles Finishing 34 | 18 silly install printInstalled 35 | 19 verbose stack Error: ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/plugins/external/markdown-it-vue' 36 | 20 verbose cwd /home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins 37 | 21 error Linux 5.0.0-37-generic 38 | 22 error argv "/usr/bin/node" "/usr/bin/npm" "install" "--save" "plugins/external/markdown-it-vue" 39 | 23 error node v8.10.0 40 | 24 error npm v3.5.2 41 | 25 error path /home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/plugins/external/markdown-it-vue 42 | 26 error code ENOENT 43 | 27 error errno -2 44 | 28 error syscall open 45 | 29 error enoent ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/plugins/external/markdown-it-vue' 46 | 30 error enoent ENOENT: no such file or directory, open '/home/hexisyztem/lutece/frontend/lutece-frontend/src/plugins/plugins/external/markdown-it-vue' 47 | 30 error enoent This is most likely not a problem with npm itself 48 | 30 error enoent and is related to npm not being able to find a file. 49 | 31 verbose exit [ -2, true ] 50 | -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from 'register-service-worker'; 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | register(`${process.env.BASE_URL}static/js/service-worker.js`, { 7 | ready() { 8 | console.log('App is being served from cache by a service worker.\n' 9 | + 'For more details, visit https://goo.gl/AFskqB'); 10 | }, 11 | cached() { 12 | console.log('Content has been cached for offline use.'); 13 | }, 14 | updated() { 15 | console.log('New content is available; please refresh.'); 16 | }, 17 | offline() { 18 | console.log('No internet connection found. App is running in offline mode.'); 19 | }, 20 | error(error) { 21 | console.error('Error during service worker registration:', error); 22 | }, 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/router/about/router.js: -------------------------------------------------------------------------------- 1 | import About from '@/components/about/about'; 2 | 3 | const Router = [ 4 | { 5 | path: '/about', 6 | name: 'About', 7 | component: About, 8 | }, 9 | ]; 10 | 11 | export default Router; 12 | -------------------------------------------------------------------------------- /src/router/article/router.js: -------------------------------------------------------------------------------- 1 | import HomeArticleEdit from '@/components/article/home/edit'; 2 | import HomeArticleCreate from '@/components/article/home/create'; 3 | import UserArticleEdit from '@/components/article/user/edit'; 4 | import UserArticleCreate from '@/components/article/user/create'; 5 | 6 | const Router = [ 7 | { 8 | path: '/article/home/edit/:slug', 9 | name: 'HomeArticleEdit', 10 | component: HomeArticleEdit, 11 | props: true, 12 | meta: { 13 | requirePermission: 'article.change_homearticle', 14 | }, 15 | }, 16 | { 17 | path: '/article/home/create', 18 | name: 'HomeArticleCreate', 19 | component: HomeArticleCreate, 20 | meta: { 21 | requirePermission: 'article.add_homearticle', 22 | }, 23 | }, 24 | { 25 | path: '/article/user/edit/:pk', 26 | name: 'UserArticleEdit', 27 | component: UserArticleEdit, 28 | props: true, 29 | meta: { 30 | requirePermission: 'article.change_userarticle', 31 | }, 32 | }, 33 | { 34 | path: '/article/user/create', 35 | name: 'UserArticleCreate', 36 | component: UserArticleCreate, 37 | meta: { 38 | requirePermission: 'article.add_userarticle', 39 | }, 40 | }, 41 | ]; 42 | 43 | export default Router; 44 | -------------------------------------------------------------------------------- /src/router/contest/router.js: -------------------------------------------------------------------------------- 1 | import ContestList from '@/components/contest/list/app'; 2 | import ContestCreate from '@/components/contest/edit/create'; 3 | import ContestDetail from '@/components/contest/detail/app'; 4 | import ContestReview from '@/components/contest/review/app'; 5 | import ContestRank from '@/components/contest/detail/rank'; 6 | import ContestReviewOverall from '@/components/contest/review/overall'; 7 | import ContestReviewMine from '@/components/contest/review/mine'; 8 | import ContestUpdate from '@/components/contest/edit/update'; 9 | 10 | const Router = [ 11 | { 12 | path: '/contest', 13 | name: 'Contest', 14 | component: ContestList, 15 | }, 16 | { 17 | path: '/contest-create', 18 | name: 'ContestCreate', 19 | component: ContestCreate, 20 | meta: { 21 | requirePermission: 'contest.add_contest', 22 | }, 23 | }, 24 | { 25 | path: '/contest-update/:pk', 26 | name: 'ContestUpdate', 27 | component: ContestUpdate, 28 | meta: { 29 | requirePermission: 'contest.change_contest', 30 | }, 31 | props: true, 32 | }, 33 | { 34 | path: '/contest/:pk', 35 | name: 'ContestDetail', 36 | component: ContestDetail, 37 | redirect: { 38 | name: 'ContestSummary', 39 | }, 40 | children: [ 41 | { 42 | path: 'summary', 43 | name: 'ContestSummary', 44 | }, 45 | { 46 | path: 'clarification', 47 | name: 'ContestClarification', 48 | }, 49 | { 50 | path: 'problem', 51 | name: 'ContestProblem', 52 | children: [{ 53 | path: ':id', 54 | name: 'ContestSpecifyProblem', 55 | }], 56 | }, 57 | { 58 | path: 'submit', 59 | name: 'ContestSubmissionSubmit', 60 | }, 61 | { 62 | path: 'submission', 63 | name: 'ContestSubmission', 64 | }, 65 | { 66 | path: 'rank', 67 | name: 'ContestRank', 68 | component: ContestRank, 69 | props: true, 70 | }, 71 | ], 72 | props: true, 73 | }, 74 | { 75 | path: '/contest-review/:pk', 76 | name: 'ContestReview', 77 | component: ContestReview, 78 | redirect: { 79 | name: 'ContestReviewOverall', 80 | }, 81 | children: [ 82 | { 83 | path: 'overall', 84 | name: 'ContestReviewOverall', 85 | component: ContestReviewOverall, 86 | props: true, 87 | }, 88 | { 89 | path: 'mine', 90 | name: 'ContestReviewMine', 91 | component: ContestReviewMine, 92 | props: true, 93 | meta: { 94 | requireAuth: true, 95 | }, 96 | }, 97 | ], 98 | props: true, 99 | }, 100 | ]; 101 | 102 | 103 | export default Router; 104 | -------------------------------------------------------------------------------- /src/router/home/router.js: -------------------------------------------------------------------------------- 1 | import Home from '@/components/home/home'; 2 | import Announcement from '@/components/home/announcement'; 3 | import Develop from '@/components/home/develop'; 4 | import AnnouncementDetail from '@/components/article/home/detail'; 5 | import Honor from '@/components/home/honor'; 6 | import Blog from '@/components/home/blog'; 7 | import BlogDetail from '@/components/article/user/detail'; 8 | 9 | const Router = [ 10 | { 11 | path: '', 12 | redirect: { 13 | name: 'Home', 14 | }, 15 | }, 16 | { 17 | path: '/home', 18 | name: 'Home', 19 | component: Home, 20 | }, 21 | { 22 | path: '/announcement', 23 | name: 'Announcement', 24 | component: Announcement, 25 | }, 26 | { 27 | path: '/announcement/:slug', 28 | name: 'AnnouncementDetail', 29 | component: AnnouncementDetail, 30 | props: true, 31 | }, 32 | { 33 | path: '/develop', 34 | name: 'Develop', 35 | component: Develop, 36 | }, 37 | { 38 | path: '/honor', 39 | name: 'Honor', 40 | component: Honor, 41 | }, 42 | { 43 | path: '/blog', 44 | name: 'Blog', 45 | component: Blog, 46 | }, 47 | { 48 | path: '/blog/:pk', 49 | name: 'BlogDetail', 50 | component: BlogDetail, 51 | props: true, 52 | }, 53 | ]; 54 | 55 | export default Router; 56 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import NotFound from '@/components/global/404'; 4 | import AboutRouter from '@/router/about/router'; 5 | import ArticleRouter from '@/router/article/router'; 6 | import ContestRouter from '@/router/contest/router'; 7 | import HomeRouter from '@/router/home/router'; 8 | import ProblemRouter from '@/router/problem/router'; 9 | import SignRouter from '@/router/sign/router'; 10 | import StatusRouter from '@/router/status/router'; 11 | import UserRouter from '@/router/user/router'; 12 | import Store from '@/store/index'; 13 | 14 | import { isAuthenticated, goHome, hasPermission } from './utils'; 15 | 16 | /* 17 | 18 | Supporting meta: 19 | 20 | requireAuth: boolean 21 | - true: Must log in to access. 22 | - false: Must not log in to access. 23 | 24 | requirePermission: str 25 | check the permission to login the specific route. 26 | 27 | */ 28 | 29 | const router = new Router({ 30 | mode: 'history', 31 | routes: [ 32 | ...AboutRouter, 33 | ...ArticleRouter, 34 | ...HomeRouter, 35 | ...ProblemRouter, 36 | ...SignRouter, 37 | ...StatusRouter, 38 | ...UserRouter, 39 | ...ContestRouter, 40 | { 41 | path: '*', 42 | name: '404', 43 | component: NotFound, 44 | }, 45 | ], 46 | }); 47 | 48 | 49 | router.beforeEach((to, from, next) => { 50 | const { matched } = to; 51 | 52 | for (let i = 0; i < matched.length; i += 1) { 53 | const { meta } = matched[i]; 54 | // Check the requireAuth meta info 55 | if (Object.prototype.hasOwnProperty.call(meta, 'requireAuth')) { 56 | if (isAuthenticated() !== meta.requireAuth) { 57 | goHome(); 58 | return; 59 | } 60 | } 61 | 62 | // Check the permission required or not 63 | if (Object.prototype.hasOwnProperty.call(meta, 'requirePermission')) { 64 | if (!hasPermission(meta.requirePermission)) { 65 | goHome(); 66 | return; 67 | } 68 | } 69 | } 70 | 71 | // Refresh token before enter any router, any error should be ignored, 72 | // this step is only to validate token expired or not. 73 | Store.dispatch('user/refresh_token') 74 | .then(() => next()) 75 | .catch(() => next()); 76 | }); 77 | 78 | 79 | // Reset the title 80 | router.beforeEach((to, from, next) => { 81 | Store.commit('navbar/setTitle', 'Lutece'); 82 | 83 | next(); 84 | }); 85 | 86 | Vue.use(Router); 87 | 88 | export default router; 89 | -------------------------------------------------------------------------------- /src/router/problem/router.js: -------------------------------------------------------------------------------- 1 | import ProblemDetail from '@/components/problem/detail/app'; 2 | import ProblemList from '@/components/problem/list/app'; 3 | import ProblemDescription from '@/components/problem/detail/description'; 4 | import ProblemEditor from '@/components/problem/detail/editor'; 5 | import ProblemChange from '@/components/problem/edit/app'; 6 | import ProblemCreate from '@/components/problem/create/app'; 7 | 8 | const Router = [ 9 | { 10 | path: '/problem', 11 | name: 'Problem', 12 | component: ProblemList, 13 | }, 14 | { 15 | path: '/problem-create', 16 | name: 'ProblemCreate', 17 | component: ProblemCreate, 18 | meta: { 19 | requirePermission: 'problem.add_problem', 20 | }, 21 | }, 22 | { 23 | path: '/problem/:slug', 24 | name: 'ProblemDetail', 25 | component: ProblemDetail, 26 | redirect: { 27 | name: 'ProblemDescription', 28 | }, 29 | children: [ 30 | { 31 | path: 'description', 32 | name: 'ProblemDescription', 33 | component: ProblemDescription, 34 | props: true, 35 | }, 36 | { 37 | path: 'editor', 38 | name: 'ProblemEditor', 39 | component: ProblemEditor, 40 | props: true, 41 | }, 42 | ], 43 | props: true, 44 | }, 45 | { 46 | path: '/problem-edit/:slug', 47 | name: 'ProblemEdit', 48 | component: ProblemChange, 49 | props: true, 50 | meta: { 51 | requirePermission: 'problem.change_problem', 52 | }, 53 | }, 54 | ]; 55 | 56 | 57 | export default Router; 58 | -------------------------------------------------------------------------------- /src/router/sign/router.js: -------------------------------------------------------------------------------- 1 | import Login from '@/components/signin/login'; 2 | import Signup from '@/components/signin/signup'; 3 | import Signout from '@/components/signin/signout'; 4 | 5 | const Router = [ 6 | { 7 | path: '/login', 8 | name: 'Login', 9 | component: Login, 10 | meta: { 11 | requireAuth: false, 12 | }, 13 | }, 14 | { 15 | path: '/signup', 16 | name: 'Signup', 17 | component: Signup, 18 | meta: { 19 | requireAuth: false, 20 | }, 21 | }, 22 | { 23 | path: '/signout', 24 | name: 'Signout', 25 | component: Signout, 26 | meta: { 27 | requireAuth: true, 28 | }, 29 | }, 30 | ]; 31 | 32 | 33 | export default Router; 34 | -------------------------------------------------------------------------------- /src/router/status/router.js: -------------------------------------------------------------------------------- 1 | import StatusList from '@/components/status/list/app'; 2 | import StatusDetail from '@/components/status/detail/app'; 3 | 4 | const Router = [ 5 | { 6 | path: '/status', 7 | name: 'Status', 8 | component: StatusList, 9 | }, 10 | { 11 | path: '/status/:pk', 12 | name: 'StatusDetail', 13 | component: StatusDetail, 14 | props: true, 15 | }, 16 | ]; 17 | 18 | 19 | export default Router; 20 | -------------------------------------------------------------------------------- /src/router/user/router.js: -------------------------------------------------------------------------------- 1 | import UserList from '@/components/user/list/app'; 2 | import UserDetail from '@/components/user/detail/app'; 3 | import UserSettings from '@/components/user/settings/app'; 4 | 5 | const Router = [ 6 | { 7 | path: '/user', 8 | name: 'User', 9 | component: UserList, 10 | }, 11 | { 12 | path: '/user/settings', 13 | name: 'UserSettings', 14 | component: UserSettings, 15 | meta: { 16 | requireAuth: true, 17 | }, 18 | }, 19 | { 20 | path: '/user/:username', 21 | name: 'UserDetail', 22 | component: UserDetail, 23 | props: true, 24 | }, 25 | ]; 26 | 27 | 28 | export default Router; 29 | -------------------------------------------------------------------------------- /src/router/utils.js: -------------------------------------------------------------------------------- 1 | import router from '@/router/index'; 2 | import store from '@/store'; 3 | 4 | const goBack = () => (window.history.length > 1 ? router.go(-1) : router.push('/')); 5 | 6 | const goHome = () => (router.push('/')); 7 | 8 | const isAuthenticated = () => store.getters['user/isAuthenticated']; 9 | 10 | const hasPermission = permission => store.getters['user/hasPermission'](permission); 11 | 12 | export { 13 | goHome, goBack, isAuthenticated, hasPermission, 14 | }; 15 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/src/store/actions.js -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import user from './modules/user'; 4 | import snackbar from './modules/snackbar'; 5 | import navbar from './modules/navbar'; 6 | import footer from './modules/footer'; 7 | import editor from './modules/editor'; 8 | 9 | 10 | Vue.use(Vuex); 11 | 12 | export default new Vuex.Store({ 13 | modules: { 14 | user, 15 | snackbar, 16 | navbar, 17 | footer, 18 | editor, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/store/modules/editor.js: -------------------------------------------------------------------------------- 1 | /* eslint no-shadow: ["error", { "allow": ["state"] }] */ 2 | 3 | import Language from '@/modules/language/main'; 4 | import Keymap from '@/modules/code-mirror/keymap'; 5 | import Theme from '@/modules/code-mirror/theme'; 6 | 7 | export const state = () => ({ 8 | currentLanguage: Language.valueOf(localStorage.getItem('editor_language')), 9 | importLanguage: null, 10 | currentKeymap: Keymap.valueOf(localStorage.getItem('editor_keymap')), 11 | importKeymap: null, 12 | currentTheme: Theme.valueOf(localStorage.getItem('editor_theme')), 13 | importTheme: null, 14 | }); 15 | 16 | export const mutations = { 17 | setCurrentLanguage(state, lang) { 18 | localStorage.setItem('editor_language', lang.full); 19 | state.currentLanguage = lang; 20 | }, 21 | setImportLanguage(state, lang) { 22 | if (state.currentLanguage === lang) { 23 | state.importLanguage = lang; 24 | } 25 | }, 26 | setCurrentKeymap(state, keymap) { 27 | localStorage.setItem('editor_keymap', keymap.name); 28 | state.currentKeymap = keymap; 29 | }, 30 | setImportKeymap(state, keymap) { 31 | if (state.currentKeymap === keymap) { 32 | state.importKeymap = keymap; 33 | } 34 | }, 35 | setCurrentTheme(state, theme) { 36 | localStorage.setItem('editor_theme', theme.name); 37 | state.currentTheme = theme; 38 | }, 39 | setImportTheme(state, theme) { 40 | if (state.currentTheme === theme) { 41 | state.importTheme = theme; 42 | } 43 | }, 44 | }; 45 | 46 | const getters = { 47 | currentLanguage: state => state.currentLanguage, 48 | importLanguage: state => state.importLanguage, 49 | currentKeymap: state => state.currentKeymap, 50 | importKeymap: state => state.importKeymap, 51 | currentTheme: state => state.currentTheme, 52 | importTheme: state => state.importTheme, 53 | }; 54 | 55 | const actions = { 56 | updateLanguage({ commit }, lang) { 57 | commit('setCurrentLanguage', lang); 58 | lang.codeMirrorImport().then(() => { 59 | commit('setImportLanguage', lang); 60 | }); 61 | }, 62 | updateKeymap({ commit }, keymap) { 63 | commit('setCurrentKeymap', keymap); 64 | keymap.import().then(() => { 65 | commit('setImportKeymap', keymap); 66 | }); 67 | }, 68 | updateTheme({ commit }, theme) { 69 | commit('setCurrentTheme', theme); 70 | theme.import().then(() => { 71 | commit('setImportTheme', theme); 72 | }); 73 | }, 74 | }; 75 | 76 | export default { 77 | namespaced: true, 78 | state, 79 | actions, 80 | getters, 81 | mutations, 82 | }; 83 | -------------------------------------------------------------------------------- /src/store/modules/footer.js: -------------------------------------------------------------------------------- 1 | /* eslint no-shadow: ["error", { "allow": ["state"] }] */ 2 | 3 | export const state = () => ({ 4 | visible: true, 5 | }); 6 | 7 | export const mutations = { 8 | setVisible(state, visible) { 9 | state.visible = (visible === true); 10 | }, 11 | }; 12 | 13 | const getters = { 14 | visible: state => state.visible, 15 | }; 16 | 17 | const actions = { 18 | setVisible({ flag, commit }) { 19 | commit('setVisible', flag); 20 | }, 21 | }; 22 | 23 | export default { 24 | namespaced: true, 25 | state, 26 | actions, 27 | getters, 28 | mutations, 29 | }; 30 | -------------------------------------------------------------------------------- /src/store/modules/navbar.js: -------------------------------------------------------------------------------- 1 | /* eslint no-shadow: ["error", { "allow": ["state"] }] */ 2 | 3 | export const state = () => ({ 4 | visible: 0, 5 | title: 'Lutece', 6 | }); 7 | 8 | export const mutations = { 9 | setProgressVisible(state, visible) { 10 | if (visible === true) { 11 | state.visible += 1; 12 | } else if (visible === false) { 13 | state.visible -= 1; 14 | } 15 | }, 16 | setTitle(state, title) { 17 | state.title = title || 'Lutece'; 18 | }, 19 | }; 20 | 21 | const getters = { 22 | progressVisible: state => (state.visible > 0), 23 | title: state => state.title, 24 | }; 25 | 26 | export default { 27 | namespaced: true, 28 | state, 29 | getters, 30 | mutations, 31 | }; 32 | -------------------------------------------------------------------------------- /src/store/modules/snackbar.js: -------------------------------------------------------------------------------- 1 | /* eslint no-shadow: ["error", { "allow": ["state"] }] */ 2 | 3 | export const state = () => ({ 4 | snack: '', 5 | }); 6 | 7 | export const mutations = { 8 | setSnack(state, snack) { 9 | state.snack = snack; 10 | }, 11 | }; 12 | 13 | export default { 14 | namespaced: true, 15 | state, 16 | mutations, 17 | }; 18 | -------------------------------------------------------------------------------- /src/store/mutations.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lutece-awesome/lutece-frontend/27126931178a87e2a73e209b476b0c96517fc09e/src/store/mutations.js -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import apolloProvider from '@/plugins/essential/apollo-provider'; 2 | 3 | function getServerUri(protocol, path) { 4 | const env = process.env.NODE_ENV; 5 | const loc = window.location; 6 | const host = env === 'production' ? loc.host : `${loc.host.split(':')[0]}:8000`; 7 | let newUri = protocol; 8 | if (loc.protocol === 'https:') { 9 | newUri += 's:'; 10 | } else { 11 | newUri += ':'; 12 | } 13 | // newUri += ':'; 14 | // const socket_ip_port = '127.0.0.1:8001'; 15 | // newUri += `//${socket_ip_port}/${path}`; 16 | newUri += `//${host}/${path}`; 17 | return newUri; 18 | } 19 | 20 | function getGraphQLUri() { 21 | return getServerUri('http', 'graphql'); 22 | } 23 | 24 | function getWebSocketUri() { 25 | return getServerUri('ws', 'ws'); 26 | } 27 | 28 | function getThoundNumberic(number) { 29 | return number < 1000 ? String(number) : `${String((number / 1000).toFixed(1))}K`; 30 | } 31 | 32 | function formatRank(rk) { 33 | let s = String(rk); 34 | if (s.length < 2) s = `0${s}`; 35 | return s; 36 | } 37 | 38 | function clearApolloCache() { 39 | return apolloProvider.defaultClient.resetStore(); 40 | } 41 | 42 | // To use this function gain the specific fied error message, the catch error must be 43 | // parsing result of function parseGraphqlError. 44 | function getErrorMessage(error, field) { 45 | if (error && Object.prototype.hasOwnProperty.call(error, field)) { 46 | return error[field][0].message; 47 | } 48 | return ''; 49 | } 50 | 51 | function parseGraphqlError(error) { 52 | return JSON.parse(error.graphQLErrors[0].message); 53 | } 54 | 55 | const { version } = require('../package.json'); 56 | 57 | export { 58 | getGraphQLUri, getWebSocketUri, getServerUri, getThoundNumberic, 59 | formatRank, version, clearApolloCache, getErrorMessage, parseGraphqlError, 60 | }; 61 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const FontminPlugin = require('fontmin-webpack'); 3 | const PurgecssPlugin = require('purgecss-webpack-plugin'); 4 | const path = require('path'); 5 | const glob = require('glob-all'); 6 | 7 | class MyFontminPlugin extends FontminPlugin { 8 | findFontFiles(compilation) { 9 | const regular = this.findRegularFontFiles(compilation); 10 | const extract = this.findExtractTextFontFiles(compilation); 11 | return _.filter( 12 | _.uniqBy(regular.concat(extract), 'asset'), 13 | o => !o.asset.includes('KaTeX'), 14 | ); 15 | } 16 | 17 | apply(compiler) { 18 | compiler.hooks.compilation.tap('Fontmin', (compilation) => { 19 | compilation.hooks.additionalAssets.tap('Fontmin', () => { 20 | this.onAdditionalAssets(compilation, () => {}); 21 | }); 22 | }); 23 | } 24 | } 25 | 26 | module.exports = { 27 | pwa: { 28 | name: 'Lutece', 29 | manifestPath: 'static/icons/manifest.json', 30 | iconPaths: { 31 | favicon32: 'static/icons/favicon-32x32.png', 32 | favicon16: 'static/icons/favicon-16x16.png', 33 | appleTouchIcon: 'static/icons/apple-touch-icon-152x152.png', 34 | maskIcon: 'static/icons/safari-pinned-tab.svg', 35 | msTileImage: 'static/icons/msapplication-icon-144x144.png', 36 | }, 37 | themeColor: '#1E88E5', 38 | msTileColor: '#2B5797', 39 | appleMobileWebAppCapable: 'yes', 40 | appleMobileWebAppStatusBarStyle: 'black', 41 | workboxOptions: { 42 | swDest: 'static/js/service-worker.js', 43 | importsDirectory: 'static/js/', 44 | }, 45 | }, 46 | 47 | baseUrl: undefined, 48 | outputDir: undefined, 49 | assetsDir: 'static', 50 | runtimeCompiler: undefined, 51 | productionSourceMap: undefined, 52 | parallel: undefined, 53 | css: undefined, 54 | chainWebpack: (_config) => { 55 | _config.module 56 | .rule('md') 57 | .test(/\.md$/) 58 | .use('raw-loader') 59 | .loader('raw-loader') 60 | .end(); 61 | _config.when(process.env.NODE_ENV === 'production', (config) => { 62 | config.plugins 63 | .delete('prefetch') 64 | .end() 65 | .plugin('purgecss') 66 | .use(PurgecssPlugin, [ 67 | { 68 | paths: glob.sync([ 69 | path.join(__dirname, './index.html'), 70 | path.join(__dirname, './src/**/*.vue'), 71 | path.join(__dirname, './src/**/*.js'), 72 | path.join( 73 | __dirname, 74 | 'node_modules', 75 | 'vuetify', 76 | 'src', 77 | '**/*.@(js|ts)', 78 | ), 79 | ]), 80 | whitelistPatterns: [/^(?!mdi)/, /^mdi-alpha-./], 81 | }, 82 | ]) 83 | .after('extract-css') 84 | .end() 85 | .plugin('fontmin') 86 | .use(MyFontminPlugin) 87 | .end(); 88 | }); 89 | }, 90 | }; 91 | --------------------------------------------------------------------------------