Просмотр исходного кода

Merge pull request #371 from knadh/upgrade-frontend

Upgrade all frontend JS deps
Kailash Nadh 4 лет назад
Родитель
Сommit
e54c33e8e8

+ 19 - 22
frontend/package.json

@@ -10,42 +10,39 @@
   },
   },
   "dependencies": {
   "dependencies": {
     "axios": "^0.21.1",
     "axios": "^0.21.1",
-    "buefy": "^0.8.20",
-    "c3": "^0.7.18",
+    "buefy": "^0.9.7",
+    "c3": "^0.7.20",
     "codeflask": "^1.4.1",
     "codeflask": "^1.4.1",
-    "core-js": "^3.6.5",
-    "dayjs": "^1.8.28",
-    "elliptic": "^6.5.4",
+    "core-js": "^3.12.1",
+    "dayjs": "^1.10.4",
     "humps": "^2.0.1",
     "humps": "^2.0.1",
-    "lodash": "^4.17.21",
-    "node-forge": "^0.10.0",
-    "node-sass": "^4.14.1",
-    "qs": "^6.9.4",
+    "qs": "^6.10.1",
     "quill": "^1.3.7",
     "quill": "^1.3.7",
     "quill-delta": "^4.2.2",
     "quill-delta": "^4.2.2",
-    "sass-loader": "^8.0.2",
     "textversionjs": "^1.1.3",
     "textversionjs": "^1.1.3",
     "turndown": "^7.0.0",
     "turndown": "^7.0.0",
-    "vue": "^2.6.11",
+    "vue": "^2.6.12",
     "vue-c3": "^1.2.11",
     "vue-c3": "^1.2.11",
     "vue-i18n": "^8.22.2",
     "vue-i18n": "^8.22.2",
     "vue-quill-editor": "^3.0.6",
     "vue-quill-editor": "^3.0.6",
     "vue-router": "^3.2.0",
     "vue-router": "^3.2.0",
-    "vuex": "^3.4.0"
+    "vuex": "^3.6.2"
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@vue/cli-plugin-babel": "~4.4.0",
-    "@vue/cli-plugin-eslint": "~4.4.0",
-    "@vue/cli-plugin-router": "~4.4.0",
-    "@vue/cli-plugin-vuex": "~4.4.0",
-    "@vue/cli-service": "~4.4.0",
-    "@vue/eslint-config-airbnb": "^5.0.2",
+    "@vue/cli-plugin-babel": "~4.5.13",
+    "@vue/cli-plugin-eslint": "~4.5.13",
+    "@vue/cli-plugin-router": "~4.5.13",
+    "@vue/cli-plugin-vuex": "~4.5.13",
+    "@vue/cli-service": "~4.5.13",
+    "@vue/eslint-config-airbnb": "^5.3.0",
     "babel-eslint": "^10.1.0",
     "babel-eslint": "^10.1.0",
     "cypress": "^6.4.0",
     "cypress": "^6.4.0",
     "cypress-file-upload": "^5.0.2",
     "cypress-file-upload": "^5.0.2",
-    "eslint": "^6.7.2",
-    "eslint-plugin-import": "^2.20.2",
-    "eslint-plugin-vue": "^6.2.2",
-    "vue-template-compiler": "^2.6.11"
+    "eslint": "^7.27.0",
+    "eslint-plugin-import": "^2.23.3",
+    "eslint-plugin-vue": "^7.9.0",
+    "sass": "^1.34.0",
+    "sass-loader": "^10.2.0",
+    "vue-template-compiler": "^2.6.12"
   }
   }
 }
 }

+ 0 - 44
frontend/src/assets/buefy.scss

@@ -1,44 +0,0 @@
-@import "~bulma/sass/base/_all";
-@import "~bulma/sass/elements/_all";
-@import "~bulma/sass/components/card";
-@import "~bulma/sass/components/dropdown";
-@import "~bulma/sass/components/level";
-@import "~bulma/sass/components/menu";
-@import "~bulma/sass/components/message";
-@import "~bulma/sass/components/modal";
-@import "~bulma/sass/components/navbar";
-@import "~bulma/sass/components/pagination";
-@import "~bulma/sass/components/tabs";
-@import "~bulma/sass/form/_all";
-@import "~bulma/sass/grid/columns";
-@import "~bulma/sass/grid/tiles";
-@import "~bulma/sass/layout/section";
-@import "~bulma/sass/layout/footer";
-
-@import "~buefy/src/scss/utils/_all";
-@import "~buefy/src/scss/components/_autocomplete";
-@import "~buefy/src/scss/components/_carousel";
-@import "~buefy/src/scss/components/_checkbox";
-@import "~buefy/src/scss/components/_datepicker";
-@import "~buefy/src/scss/components/_dialog";
-@import "~buefy/src/scss/components/_dropdown";
-@import "~buefy/src/scss/components/_form";
-@import "~buefy/src/scss/components/_icon";
-@import "~buefy/src/scss/components/_loading";
-@import "~buefy/src/scss/components/_menu";
-@import "~buefy/src/scss/components/_message";
-@import "~buefy/src/scss/components/_modal";
-@import "~buefy/src/scss/components/_pagination";
-@import "~buefy/src/scss/components/_notices";
-@import "~buefy/src/scss/components/_progress";
-@import "~buefy/src/scss/components/_radio";
-@import "~buefy/src/scss/components/_select";
-@import "~buefy/src/scss/components/_sidebar";
-@import "~buefy/src/scss/components/_switch";
-@import "~buefy/src/scss/components/_table";
-@import "~buefy/src/scss/components/_tabs";
-@import "~buefy/src/scss/components/_tag";
-@import "~buefy/src/scss/components/_taginput";
-@import "~buefy/src/scss/components/_timepicker";
-@import "~buefy/src/scss/components/_tooltip";
-@import "~buefy/src/scss/components/_upload";

+ 5 - 4
frontend/src/assets/style.scss

@@ -30,10 +30,11 @@ $modal-background-background-color: rgba(0, 0, 0, .30);
 $speed-slow: 25ms !default;
 $speed-slow: 25ms !default;
 $speed-slower: 50ms !default;
 $speed-slower: 50ms !default;
 
 
-/* Import full Bulma and Buefy to override styles. */
-// @import "~bulma";
-@import "./buefy";
+/* Import full Bulma and Buefy */
+@import "~bulma";
+@import "~buefy/src/scss/buefy";
 
 
+/* Custom style overrides */
 html, body {
 html, body {
   height: 100%;
   height: 100%;
 }
 }
@@ -752,6 +753,7 @@ section.campaign {
 
 
   /* Hide sidebar menu captions on mobile */
   /* Hide sidebar menu captions on mobile */
   .b-sidebar .sidebar-content.is-mini-mobile {
   .b-sidebar .sidebar-content.is-mini-mobile {
+    max-width: 90px;
     .menu-list {
     .menu-list {
       li {
       li {
         margin-bottom: 30px;
         margin-bottom: 30px;
@@ -773,7 +775,6 @@ section.campaign {
 
 
   td .tags {
   td .tags {
     display: block;
     display: block;
-    text-align: right;
 
 
     .tag:not(:last-child) {
     .tag:not(:last-child) {
       margin-right: 0;
       margin-right: 0;

+ 4 - 0
frontend/src/utils.js

@@ -118,4 +118,8 @@ export default class Utils {
       duration: duration || 2000,
       duration: duration || 2000,
     });
     });
   };
   };
+
+  // Takes a props.row from a Buefy b-column <td> template and
+  // returns a `data-id` attribute which Buefy then applies to the td.
+  tdID = (row) => ({ 'data-id': row.id.toString() });
 }
 }

+ 165 - 167
frontend/src/views/Campaigns.vue

@@ -29,175 +29,173 @@
       paginated backend-pagination pagination-position="both" @page-change="onPageChange"
       paginated backend-pagination pagination-position="both" @page-change="onPageChange"
       :current-page="queryParams.page" :per-page="campaigns.perPage" :total="campaigns.total"
       :current-page="queryParams.page" :per-page="campaigns.perPage" :total="campaigns.total"
       hoverable backend-sorting @sort="onSort">
       hoverable backend-sorting @sort="onSort">
-        <template slot-scope="props">
-            <b-table-column class="status" field="status" :label="$t('globals.fields.status')"
-              width="10%" :id="props.row.id" sortable
-              header-class="cy-status" :data-id="props.row.id">
-              <div>
-                <p>
-                  <router-link :to="{ name: 'campaign', params: { 'id': props.row.id }}">
-                    <b-tag :class="props.row.status">
-                      {{ $t(`campaigns.status.${props.row.status}`) }}
-                    </b-tag>
-                    <span class="spinner is-tiny" v-if="isRunning(props.row.id)">
-                      <b-loading :is-full-page="false" active />
-                    </span>
-                  </router-link>
-                </p>
-                <p v-if="isSheduled(props.row)">
-                  <b-tooltip :label="$t('scheduled')" type="is-dark">
-                    <span class="is-size-7 has-text-grey scheduled">
-                      <b-icon icon="alarm" size="is-small" />
-                      {{ $utils.duration(Date(), props.row.sendAt, true) }}
-                      <br />{{ $utils.niceDate(props.row.sendAt, true) }}
-                    </span>
-                  </b-tooltip>
-                </p>
-              </div>
-            </b-table-column>
-            <b-table-column field="name" :label="$t('globals.fields.name')" sortable width="25%"
-              header-class="cy-name">
-              <div>
-                <p>
-                  <b-tag v-if="props.row.type !== 'regular'" class="is-small">
-                    {{ props.row.type }}
-                  </b-tag>
-                  <router-link :to="{ name: 'campaign', params: { 'id': props.row.id }}">
-                    {{ props.row.name }}</router-link>
-                </p>
-                <p class="is-size-7 has-text-grey">{{ props.row.subject }}</p>
-                <b-taglist>
-                    <b-tag class="is-small" v-for="t in props.row.tags" :key="t">{{ t }}</b-tag>
-                </b-taglist>
-              </div>
-            </b-table-column>
-            <b-table-column class="lists" field="lists"
-              :label="$t('globals.terms.lists')" width="15%">
-              <ul class="no">
-                <li v-for="l in props.row.lists" :key="l.id">
-                  <router-link :to="{name: 'subscribers_list', params: { listID: l.id }}">
-                    {{ l.name }}
-                  </router-link>
-                </li>
-              </ul>
-            </b-table-column>
-            <b-table-column field="created_at" :label="$t('campaigns.timestamps')"
-              width="19%" sortable header-class="cy-timestamp">
-              <div class="fields timestamps" :set="stats = getCampaignStats(props.row)">
-                <p>
-                  <label>{{ $t('globals.fields.createdAt') }}</label>
-                  {{ $utils.niceDate(props.row.createdAt, true) }}
-                </p>
-                <p v-if="stats.startedAt">
-                  <label>{{ $t('campaigns.startedAt') }}</label>
-                  {{ $utils.niceDate(stats.startedAt, true) }}
-                </p>
-                <p v-if="isDone(props.row)">
-                  <label>{{ $t('campaigns.ended') }}</label>
-                  {{ $utils.niceDate(stats.updatedAt, true) }}
-                </p>
-                <p v-if="stats.startedAt && stats.updatedAt"
-                  class="is-capitalized" title="Duration">
-                  <label><b-icon icon="alarm" size="is-small" /></label>
-                  {{ $utils.duration(stats.startedAt, stats.updatedAt) }}
-                </p>
-              </div>
-            </b-table-column>
+      <b-table-column v-slot="props" class="status" field="status"
+        :label="$t('globals.fields.status')" width="10%" sortable
+        :td-attrs="$utils.tdID" header-class="cy-status">
+        <div>
+          <p>
+            <router-link :to="{ name: 'campaign', params: { 'id': props.row.id }}">
+              <b-tag :class="props.row.status">
+                {{ $t(`campaigns.status.${props.row.status}`) }}
+              </b-tag>
+              <span class="spinner is-tiny" v-if="isRunning(props.row.id)">
+                <b-loading :is-full-page="false" active />
+              </span>
+            </router-link>
+          </p>
+          <p v-if="isSheduled(props.row)">
+            <b-tooltip :label="$t('scheduled')" type="is-dark">
+              <span class="is-size-7 has-text-grey scheduled">
+                <b-icon icon="alarm" size="is-small" />
+                {{ $utils.duration(Date(), props.row.sendAt, true) }}
+                <br />{{ $utils.niceDate(props.row.sendAt, true) }}
+              </span>
+            </b-tooltip>
+          </p>
+        </div>
+      </b-table-column>
+      <b-table-column v-slot="props" field="name" :label="$t('globals.fields.name')" width="25%"
+        sortable header-class="cy-name">
+        <div>
+          <p>
+            <b-tag v-if="props.row.type !== 'regular'" class="is-small">
+              {{ props.row.type }}
+            </b-tag>
+            <router-link :to="{ name: 'campaign', params: { 'id': props.row.id }}">
+              {{ props.row.name }}</router-link>
+          </p>
+          <p class="is-size-7 has-text-grey">{{ props.row.subject }}</p>
+          <b-taglist>
+              <b-tag class="is-small" v-for="t in props.row.tags" :key="t">{{ t }}</b-tag>
+          </b-taglist>
+        </div>
+      </b-table-column>
+      <b-table-column v-slot="props" class="lists" field="lists"
+        :label="$t('globals.terms.lists')" width="15%">
+        <ul class="no">
+          <li v-for="l in props.row.lists" :key="l.id">
+            <router-link :to="{name: 'subscribers_list', params: { listID: l.id }}">
+              {{ l.name }}
+            </router-link>
+          </li>
+        </ul>
+      </b-table-column>
+      <b-table-column v-slot="props" field="created_at" :label="$t('campaigns.timestamps')"
+        width="19%" sortable header-class="cy-timestamp">
+        <div class="fields timestamps" :set="stats = getCampaignStats(props.row)">
+          <p>
+            <label>{{ $t('globals.fields.createdAt') }}</label>
+            {{ $utils.niceDate(props.row.createdAt, true) }}
+          </p>
+          <p v-if="stats.startedAt">
+            <label>{{ $t('campaigns.startedAt') }}</label>
+            {{ $utils.niceDate(stats.startedAt, true) }}
+          </p>
+          <p v-if="isDone(props.row)">
+            <label>{{ $t('campaigns.ended') }}</label>
+            {{ $utils.niceDate(stats.updatedAt, true) }}
+          </p>
+          <p v-if="stats.startedAt && stats.updatedAt"
+            class="is-capitalized" title="Duration">
+            <label><b-icon icon="alarm" size="is-small" /></label>
+            {{ $utils.duration(stats.startedAt, stats.updatedAt) }}
+          </p>
+        </div>
+      </b-table-column>
 
 
-            <b-table-column field="stats" :class="props.row.status"
-              :label="$t('campaigns.stats')" width="18%">
-              <div class="fields stats" :set="stats = getCampaignStats(props.row)">
-                <p>
-                  <label>{{ $t('campaigns.views') }}</label>
-                  {{ props.row.views }}
-                </p>
-                <p>
-                  <label>{{ $t('campaigns.clicks') }}</label>
-                  {{ props.row.clicks }}
-                </p>
-                <p>
-                  <label>{{ $t('campaigns.sent') }}</label>
-                  {{ stats.sent }} / {{ stats.toSend }}
-                </p>
-                <p title="Speed" v-if="stats.rate">
-                  <label><b-icon icon="speedometer" size="is-small"></b-icon></label>
-                  <span class="send-rate">
-                    {{ stats.rate.toFixed(0) }} / min
-                  </span>
-                </p>
-                <p v-if="isRunning(props.row.id)">
-                  <label>{{ $t('campaigns.progress') }}
-                    <span class="spinner is-tiny">
-                      <b-loading :is-full-page="false" active />
-                    </span>
-                  </label>
-                  <b-progress :value="stats.sent / stats.toSend * 100" size="is-small" />
-                </p>
-              </div>
-            </b-table-column>
+      <b-table-column v-slot="props" field="stats" :label="$t('campaigns.stats')" width="18%">
+        <div class="fields stats" :set="stats = getCampaignStats(props.row)">
+          <p>
+            <label>{{ $t('campaigns.views') }}</label>
+            {{ props.row.views }}
+          </p>
+          <p>
+            <label>{{ $t('campaigns.clicks') }}</label>
+            {{ props.row.clicks }}
+          </p>
+          <p>
+            <label>{{ $t('campaigns.sent') }}</label>
+            {{ stats.sent }} / {{ stats.toSend }}
+          </p>
+          <p title="Speed" v-if="stats.rate">
+            <label><b-icon icon="speedometer" size="is-small"></b-icon></label>
+            <span class="send-rate">
+              {{ stats.rate.toFixed(0) }} / min
+            </span>
+          </p>
+          <p v-if="isRunning(props.row.id)">
+            <label>{{ $t('campaigns.progress') }}
+              <span class="spinner is-tiny">
+                <b-loading :is-full-page="false" active />
+              </span>
+            </label>
+            <b-progress :value="stats.sent / stats.toSend * 100" size="is-small" />
+          </p>
+        </div>
+      </b-table-column>
 
 
-            <b-table-column class="actions" width="13%" align="right">
-              <div>
-                <a href="" v-if="canStart(props.row)"
-                  @click.prevent="$utils.confirm(null,
-                    () => changeCampaignStatus(props.row, 'running'))" data-cy="btn-start">
-                  <b-tooltip :label="$t('campaigns.start')" type="is-dark">
-                    <b-icon icon="rocket-launch-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="" v-if="canPause(props.row)"
-                  @click.prevent="$utils.confirm(null,
-                    () => changeCampaignStatus(props.row, 'paused'))" data-cy="btn-pause">
-                  <b-tooltip :label="$t('campaigns.pause')" type="is-dark">
-                    <b-icon icon="pause-circle-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="" v-if="canResume(props.row)"
-                  @click.prevent="$utils.confirm(null,
-                    () => changeCampaignStatus(props.row, 'running'))" data-cy="btn-resume">
-                  <b-tooltip :label="$t('campaigns.send')" type="is-dark">
-                    <b-icon icon="rocket-launch-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="" v-if="canSchedule(props.row)"
-                  @click.prevent="$utils.confirm($t('campaigns.confirmSchedule'),
-                    () => changeCampaignStatus(props.row, 'scheduled'))" data-cy="btn-schedule">
-                  <b-tooltip :label="$t('campaigns.schedule')" type="is-dark">
-                    <b-icon icon="clock-start" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="" @click.prevent="previewCampaign(props.row)" data-cy="btn-preview">
-                  <b-tooltip :label="$t('campaigns.preview')" type="is-dark">
-                    <b-icon icon="file-find-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="" @click.prevent="$utils.prompt($t('globals.buttons.clone'),
-                    { placeholder: $t('globals.fields.name'),
-                      value: $t('campaigns.copyOf', { name: props.row.name }) },
-                      (name) => cloneCampaign(name, props.row))"
-                    data-cy="btn-clone">
-                  <b-tooltip :label="$t('globals.buttons.clone')" type="is-dark">
-                    <b-icon icon="file-multiple-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="" v-if="canCancel(props.row)"
-                  @click.prevent="$utils.confirm(null,
-                    () => changeCampaignStatus(props.row, 'cancelled'))"
-                    data-cy="btn-cancel">
-                  <b-tooltip :label="$t('globals.buttons.cancel')" type="is-dark">
-                    <b-icon icon="cancel" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="" @click.prevent="$utils.confirm($tc('campaigns.confirmDelete'),
-                    () => deleteCampaign(props.row))" data-cy="btn-delete">
-                    <b-icon icon="trash-can-outline" size="is-small" />
-                </a>
-              </div>
-            </b-table-column>
-        </template>
-        <template slot="empty" v-if="!loading.campaigns">
-          <empty-placeholder />
-        </template>
+      <b-table-column v-slot="props" cell-class="actions" width="13%" align="right">
+        <div>
+          <a href="" v-if="canStart(props.row)"
+            @click.prevent="$utils.confirm(null,
+              () => changeCampaignStatus(props.row, 'running'))" data-cy="btn-start">
+            <b-tooltip :label="$t('campaigns.start')" type="is-dark">
+              <b-icon icon="rocket-launch-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="" v-if="canPause(props.row)"
+            @click.prevent="$utils.confirm(null,
+              () => changeCampaignStatus(props.row, 'paused'))" data-cy="btn-pause">
+            <b-tooltip :label="$t('campaigns.pause')" type="is-dark">
+              <b-icon icon="pause-circle-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="" v-if="canResume(props.row)"
+            @click.prevent="$utils.confirm(null,
+              () => changeCampaignStatus(props.row, 'running'))" data-cy="btn-resume">
+            <b-tooltip :label="$t('campaigns.send')" type="is-dark">
+              <b-icon icon="rocket-launch-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="" v-if="canSchedule(props.row)"
+            @click.prevent="$utils.confirm($t('campaigns.confirmSchedule'),
+              () => changeCampaignStatus(props.row, 'scheduled'))" data-cy="btn-schedule">
+            <b-tooltip :label="$t('campaigns.schedule')" type="is-dark">
+              <b-icon icon="clock-start" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="" @click.prevent="previewCampaign(props.row)" data-cy="btn-preview">
+            <b-tooltip :label="$t('campaigns.preview')" type="is-dark">
+              <b-icon icon="file-find-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="" @click.prevent="$utils.prompt($t('globals.buttons.clone'),
+              { placeholder: $t('globals.fields.name'),
+                value: $t('campaigns.copyOf', { name: props.row.name }) },
+                (name) => cloneCampaign(name, props.row))"
+              data-cy="btn-clone">
+            <b-tooltip :label="$t('globals.buttons.clone')" type="is-dark">
+              <b-icon icon="file-multiple-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="" v-if="canCancel(props.row)"
+            @click.prevent="$utils.confirm(null,
+              () => changeCampaignStatus(props.row, 'cancelled'))"
+              data-cy="btn-cancel">
+            <b-tooltip :label="$t('globals.buttons.cancel')" type="is-dark">
+              <b-icon icon="cancel" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="" @click.prevent="$utils.confirm($tc('campaigns.confirmDelete'),
+              () => deleteCampaign(props.row))" data-cy="btn-delete">
+              <b-icon icon="trash-can-outline" size="is-small" />
+          </a>
+        </div>
+      </b-table-column>
+
+      <template #empty v-if="!loading.campaigns">
+        <empty-placeholder />
+      </template>
     </b-table>
     </b-table>
 
 
     <campaign-preview v-if="previewItem"
     <campaign-preview v-if="previewItem"

+ 79 - 78
frontend/src/views/Lists.vue

@@ -22,84 +22,85 @@
       :current-page="queryParams.page" :per-page="lists.perPage" :total="lists.total"
       :current-page="queryParams.page" :per-page="lists.perPage" :total="lists.total"
       backend-sorting @sort="onSort"
       backend-sorting @sort="onSort"
     >
     >
-        <template slot-scope="props">
-            <b-table-column field="name" :label="$t('globals.fields.name')" header-class="cy-name"
-              sortable width="25%" paginated backend-pagination pagination-position="both"
-              @page-change="onPageChange" :data-id="props.row.id">
-              <div>
-                <router-link :to="{name: 'subscribers_list', params: { listID: props.row.id }}">
-                  {{ props.row.name }}
-                </router-link>
-                <b-taglist>
-                    <b-tag class="is-small" v-for="t in props.row.tags" :key="t">{{ t }}</b-tag>
-                </b-taglist>
-              </div>
-            </b-table-column>
-
-            <b-table-column field="type" :label="$t('globals.fields.type')" header-class="cy-type"
-              sortable>
-              <div>
-                <b-tag :class="props.row.type" :data-cy="`type-${props.row.type}`">
-                  {{ $t('lists.types.' + props.row.type) }}
-                </b-tag>
-                {{ ' ' }}
-                <b-tag :data-cy="`optin-${props.row.optin}`">
-                  <b-icon :icon="props.row.optin === 'double' ?
-                    'account-check-outline' : 'account-off-outline'" size="is-small" />
-                  {{ ' ' }}
-                  {{ $t('lists.optins.' + props.row.optin) }}
-                </b-tag>{{ ' ' }}
-                <a v-if="props.row.optin === 'double'" class="is-size-7 send-optin"
-                  href="#" @click="$utils.confirm(null, () => createOptinCampaign(props.row))"
-                  data-cy="btn-send-optin-campaign">
-                  <b-tooltip :label="$t('lists.sendOptinCampaign')" type="is-dark">
-                    <b-icon icon="rocket-launch-outline" size="is-small" />
-                    {{ $t('lists.sendOptinCampaign') }}
-                  </b-tooltip>
-                </a>
-              </div>
-            </b-table-column>
-
-            <b-table-column field="subscriber_count" :label="$t('globals.terms.subscribers')"
-              header-class="cy-subscribers" numeric sortable centered>
-                <router-link :to="`/subscribers/lists/${props.row.id}`">
-                  {{ props.row.subscriberCount }}
-                </router-link>
-            </b-table-column>
-
-            <b-table-column field="created_at" :label="$t('globals.fields.createdAt')"
-              header-class="cy-created_at" sortable>
-                {{ $utils.niceDate(props.row.createdAt) }}
-            </b-table-column>
-            <b-table-column field="updated_at" :label="$t('globals.fields.updatedAt')"
-              header-class="cy-updated_at" sortable>
-                {{ $utils.niceDate(props.row.updatedAt) }}
-            </b-table-column>
-
-            <b-table-column class="actions" align="right">
-              <div>
-                <router-link :to="`/campaigns/new?list_id=${props.row.id}`" data-cy="btn-campaign">
-                  <b-tooltip :label="$t('lists.sendCampaign')" type="is-dark">
-                    <b-icon icon="rocket-launch-outline" size="is-small" />
-                  </b-tooltip>
-                </router-link>
-                <a href="" @click.prevent="showEditForm(props.row)" data-cy="btn-edit">
-                  <b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
-                    <b-icon icon="pencil-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="" @click.prevent="deleteList(props.row)" data-cy="btn-delete">
-                  <b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
-                    <b-icon icon="trash-can-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-              </div>
-            </b-table-column>
-        </template>
-
-        <template slot="empty" v-if="!loading.lists">
-            <empty-placeholder />
-        </template>
+      <b-table-column v-slot="props" field="name" :label="$t('globals.fields.name')"
+        header-class="cy-name" sortable width="25%"
+        paginated backend-pagination pagination-position="both"
+        :td-attrs="$utils.tdID"
+        @page-change="onPageChange">
+        <div>
+          <router-link :to="{name: 'subscribers_list', params: { listID: props.row.id }}">
+            {{ props.row.name }}
+          </router-link>
+          <b-taglist>
+              <b-tag class="is-small" v-for="t in props.row.tags" :key="t">{{ t }}</b-tag>
+          </b-taglist>
+        </div>
+      </b-table-column>
+
+      <b-table-column v-slot="props" field="type" :label="$t('globals.fields.type')"
+        header-class="cy-type" sortable>
+        <div>
+          <b-tag :class="props.row.type" :data-cy="`type-${props.row.type}`">
+            {{ $t('lists.types.' + props.row.type) }}
+          </b-tag>
+          {{ ' ' }}
+          <b-tag :data-cy="`optin-${props.row.optin}`">
+            <b-icon :icon="props.row.optin === 'double' ?
+              'account-check-outline' : 'account-off-outline'" size="is-small" />
+            {{ ' ' }}
+            {{ $t('lists.optins.' + props.row.optin) }}
+          </b-tag>{{ ' ' }}
+          <a v-if="props.row.optin === 'double'" class="is-size-7 send-optin"
+            href="#" @click="$utils.confirm(null, () => createOptinCampaign(props.row))"
+            data-cy="btn-send-optin-campaign">
+            <b-tooltip :label="$t('lists.sendOptinCampaign')" type="is-dark">
+              <b-icon icon="rocket-launch-outline" size="is-small" />
+              {{ $t('lists.sendOptinCampaign') }}
+            </b-tooltip>
+          </a>
+        </div>
+      </b-table-column>
+
+      <b-table-column v-slot="props" field="subscriber_count"
+        :label="$t('globals.terms.subscribers')" header-class="cy-subscribers"
+        numeric sortable centered>
+        <router-link :to="`/subscribers/lists/${props.row.id}`">
+          {{ props.row.subscriberCount }}
+        </router-link>
+      </b-table-column>
+
+      <b-table-column v-slot="props" field="created_at" :label="$t('globals.fields.createdAt')"
+        header-class="cy-created_at" sortable>
+          {{ $utils.niceDate(props.row.createdAt) }}
+      </b-table-column>
+      <b-table-column v-slot="props" field="updated_at" :label="$t('globals.fields.updatedAt')"
+        header-class="cy-updated_at" sortable>
+          {{ $utils.niceDate(props.row.updatedAt) }}
+      </b-table-column>
+
+      <b-table-column v-slot="props" cell-class="actions" align="right">
+        <div>
+          <router-link :to="`/campaigns/new?list_id=${props.row.id}`" data-cy="btn-campaign">
+            <b-tooltip :label="$t('lists.sendCampaign')" type="is-dark">
+              <b-icon icon="rocket-launch-outline" size="is-small" />
+            </b-tooltip>
+          </router-link>
+          <a href="" @click.prevent="showEditForm(props.row)" data-cy="btn-edit">
+            <b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
+              <b-icon icon="pencil-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="" @click.prevent="deleteList(props.row)" data-cy="btn-delete">
+            <b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
+              <b-icon icon="trash-can-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+        </div>
+      </b-table-column>
+
+      <template #empty v-if="!loading.lists">
+          <empty-placeholder />
+      </template>
     </b-table>
     </b-table>
 
 
     <!-- Add / edit form modal -->
     <!-- Add / edit form modal -->

+ 73 - 74
frontend/src/views/Subscribers.vue

@@ -108,86 +108,85 @@
       paginated backend-pagination pagination-position="both" @page-change="onPageChange"
       paginated backend-pagination pagination-position="both" @page-change="onPageChange"
       :current-page="queryParams.page" :per-page="subscribers.perPage" :total="subscribers.total"
       :current-page="queryParams.page" :per-page="subscribers.perPage" :total="subscribers.total"
       hoverable checkable backend-sorting @sort="onSort">
       hoverable checkable backend-sorting @sort="onSort">
-        <template slot="top-left">
+        <template #top-left>
           <a href='' @click.prevent="exportSubscribers">
           <a href='' @click.prevent="exportSubscribers">
             <b-icon icon="cloud-download-outline" size="is-small" /> {{ $t('subscribers.export') }}
             <b-icon icon="cloud-download-outline" size="is-small" /> {{ $t('subscribers.export') }}
           </a>
           </a>
         </template>
         </template>
-        <template slot-scope="props">
-            <b-table-column field="status" :label="$t('globals.fields.status')"
-              header-class="cy-status" :data-id="props.row.id" sortable>
-              <a :href="`/subscribers/${props.row.id}`"
-                @click.prevent="showEditForm(props.row)">
-                <b-tag :class="props.row.status">
-                  {{ $t('subscribers.status.'+ props.row.status) }}
+        <b-table-column v-slot="props" field="status" :label="$t('globals.fields.status')"
+          header-class="cy-status" :td-attrs="$utils.tdID" sortable>
+          <a :href="`/subscribers/${props.row.id}`"
+            @click.prevent="showEditForm(props.row)">
+            <b-tag :class="props.row.status">
+              {{ $t('subscribers.status.'+ props.row.status) }}
+            </b-tag>
+          </a>
+        </b-table-column>
+
+        <b-table-column v-slot="props" field="email" :label="$t('subscribers.email')"
+          header-class="cy-email" sortable>
+          <a :href="`/subscribers/${props.row.id}`"
+            @click.prevent="showEditForm(props.row)">
+            {{ props.row.email }}
+          </a>
+          <b-taglist>
+            <template v-for="l in props.row.lists">
+              <router-link :to="`/subscribers/lists/${l.id}`"
+                v-bind:key="l.id" style="padding-right:0.5em;">
+                <b-tag :class="l.subscriptionStatus" size="is-small" :key="l.id">
+                  {{ l.name }}
+                  <sup>{{ $t('subscribers.status.'+ l.subscriptionStatus) }}</sup>
                 </b-tag>
                 </b-tag>
-              </a>
-            </b-table-column>
+              </router-link>
+            </template>
+          </b-taglist>
+        </b-table-column>
+
+        <b-table-column v-slot="props" field="name" :label="$t('globals.fields.name')"
+           header-class="cy-name" sortable>
+          <a :href="`/subscribers/${props.row.id}`"
+            @click.prevent="showEditForm(props.row)">
+            {{ props.row.name }}
+          </a>
+        </b-table-column>
 
 
-            <b-table-column field="email" :label="$t('subscribers.email')"
-              header-class="cy-email" sortable>
-              <a :href="`/subscribers/${props.row.id}`"
-                @click.prevent="showEditForm(props.row)">
-                {{ props.row.email }}
-              </a>
-              <b-taglist>
-                <template v-for="l in props.row.lists">
-                  <router-link :to="`/subscribers/lists/${l.id}`"
-                    v-bind:key="l.id" style="padding-right:0.5em;">
-                    <b-tag :class="l.subscriptionStatus" size="is-small" :key="l.id">
-                      {{ l.name }}
-                      <sup>{{ $t('subscribers.status.'+ l.subscriptionStatus) }}</sup>
-                    </b-tag>
-                  </router-link>
-                </template>
-              </b-taglist>
-            </b-table-column>
-
-            <b-table-column field="name" :label="$t('globals.fields.name')"
-               header-class="cy-name" sortable>
-              <a :href="`/subscribers/${props.row.id}`"
-                @click.prevent="showEditForm(props.row)">
-                {{ props.row.name }}
-              </a>
-            </b-table-column>
-
-            <b-table-column field="lists" :label="$t('globals.terms.lists')"
-              header-class="cy-lists" numeric centered>
-              {{ listCount(props.row.lists) }}
-            </b-table-column>
-
-            <b-table-column field="created_at" :label="$t('globals.fields.createdAt')"
-              header-class="cy-created_at" sortable>
-                {{ $utils.niceDate(props.row.createdAt) }}
-            </b-table-column>
-
-            <b-table-column field="updated_at" :label="$t('globals.fields.updatedAt')"
-              header-class="cy-updated_at" sortable>
-                {{ $utils.niceDate(props.row.updatedAt) }}
-            </b-table-column>
-
-            <b-table-column class="actions" align="right">
-              <div>
-                <a :href="`/api/subscribers/${props.row.id}/export`" data-cy="btn-download">
-                  <b-tooltip :label="$t('subscribers.downloadData')" type="is-dark">
-                    <b-icon icon="cloud-download-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a :href="`/subscribers/${props.row.id}`"
-                  @click.prevent="showEditForm(props.row)" data-cy="btn-edit">
-                  <b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
-                    <b-icon icon="pencil-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href='' @click.prevent="deleteSubscriber(props.row)" data-cy="btn-delete">
-                  <b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
-                    <b-icon icon="trash-can-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-              </div>
-            </b-table-column>
-        </template>
-        <template slot="empty" v-if="!loading.subscribers">
+        <b-table-column v-slot="props" field="lists" :label="$t('globals.terms.lists')"
+          header-class="cy-lists" centered>
+          {{ listCount(props.row.lists) }}
+        </b-table-column>
+
+        <b-table-column v-slot="props" field="created_at" :label="$t('globals.fields.createdAt')"
+          header-class="cy-created_at" sortable>
+            {{ $utils.niceDate(props.row.createdAt) }}
+        </b-table-column>
+
+        <b-table-column v-slot="props" field="updated_at" :label="$t('globals.fields.updatedAt')"
+          header-class="cy-updated_at" sortable>
+            {{ $utils.niceDate(props.row.updatedAt) }}
+        </b-table-column>
+
+        <b-table-column v-slot="props" label="Actions" cell-class="actions" align="right">
+          <div>
+            <a :href="`/api/subscribers/${props.row.id}/export`" data-cy="btn-download">
+              <b-tooltip :label="$t('subscribers.downloadData')" type="is-dark">
+                <b-icon icon="cloud-download-outline" size="is-small" />
+              </b-tooltip>
+            </a>
+            <a :href="`/subscribers/${props.row.id}`"
+              @click.prevent="showEditForm(props.row)" data-cy="btn-edit">
+              <b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
+                <b-icon icon="pencil-outline" size="is-small" />
+              </b-tooltip>
+            </a>
+            <a href='' @click.prevent="deleteSubscriber(props.row)" data-cy="btn-delete">
+              <b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
+                <b-icon icon="trash-can-outline" size="is-small" />
+              </b-tooltip>
+            </a>
+          </div>
+        </b-table-column>
+
+        <template #empty v-if="!loading.subscribers">
           <empty-placeholder />
           <empty-placeholder />
         </template>
         </template>
     </b-table>
     </b-table>

+ 65 - 64
frontend/src/views/Templates.vue

@@ -14,70 +14,71 @@
 
 
     <b-table :data="templates" :hoverable="true" :loading="loading.templates"
     <b-table :data="templates" :hoverable="true" :loading="loading.templates"
       default-sort="createdAt">
       default-sort="createdAt">
-        <template slot-scope="props">
-            <b-table-column field="name" :label="$t('globals.fields.name')" sortable>
-                <a :href="props.row.id" @click.prevent="showEditForm(props.row)">
-                  {{ props.row.name }}
-                </a>
-                <b-tag v-if="props.row.isDefault">{{ $t('templates.default') }}</b-tag>
-            </b-table-column>
-
-            <b-table-column field="createdAt" :label="$t('globals.fields.createdAt')" sortable>
-                {{ $utils.niceDate(props.row.createdAt) }}
-            </b-table-column>
-
-            <b-table-column field="updatedAt" :label="$t('globals.fields.updatedAt')" sortable>
-                {{ $utils.niceDate(props.row.updatedAt) }}
-            </b-table-column>
-
-            <b-table-column class="actions" align="right">
-              <div>
-                <a href="#" @click.prevent="previewTemplate(props.row)" data-cy="btn-preview">
-                  <b-tooltip :label="$t('templates.preview')" type="is-dark">
-                    <b-icon icon="file-find-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="#" @click.prevent="showEditForm(props.row)" data-cy="btn-edit">
-                  <b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
-                    <b-icon icon="pencil-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a href="" @click.prevent="$utils.prompt(`Clone template`,
-                        { placeholder: 'Name', value: `Copy of ${props.row.name}`},
-                        (name) => cloneTemplate(name, props.row))"
-                        data-cy="btn-clone">
-                  <b-tooltip :label="$t('globals.buttons.clone')" type="is-dark">
-                    <b-icon icon="file-multiple-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <a v-if="!props.row.isDefault" href="#"
-                  @click.prevent="$utils.confirm(null, () => makeTemplateDefault(props.row))"
-                  data-cy="btn-set-default">
-                  <b-tooltip :label="$t('templates.makeDefault')" type="is-dark">
-                    <b-icon icon="check-circle-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <span v-else class="a has-text-grey-light">
-                    <b-icon icon="check-circle-outline" size="is-small" />
-                </span>
-
-                <a v-if="!props.row.isDefault" href="#"
-                  @click.prevent="$utils.confirm(null, () => deleteTemplate(props.row))"
-                  data-cy="btn-delete">
-                  <b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
-                    <b-icon icon="trash-can-outline" size="is-small" />
-                  </b-tooltip>
-                </a>
-                <span v-else class="a has-text-grey-light">
-                    <b-icon icon="trash-can-outline" size="is-small" />
-                </span>
-              </div>
-            </b-table-column>
-        </template>
-
-        <template slot="empty" v-if="!loading.templates">
-          <empty-placeholder />
-        </template>
+      <b-table-column v-slot="props" field="name" :label="$t('globals.fields.name')"
+        :td-attrs="$utils.tdID" sortable>
+        <a :href="props.row.id" @click.prevent="showEditForm(props.row)">
+          {{ props.row.name }}
+        </a>
+        <b-tag v-if="props.row.isDefault">{{ $t('templates.default') }}</b-tag>
+      </b-table-column>
+
+      <b-table-column v-slot="props" field="createdAt"
+        :label="$t('globals.fields.createdAt')" sortable>
+        {{ $utils.niceDate(props.row.createdAt) }}
+      </b-table-column>
+
+      <b-table-column v-slot="props" field="updatedAt"
+        :label="$t('globals.fields.updatedAt')" sortable>
+        {{ $utils.niceDate(props.row.updatedAt) }}
+      </b-table-column>
+
+      <b-table-column v-slot="props" cell-class="actions" align="right">
+        <div>
+          <a href="#" @click.prevent="previewTemplate(props.row)" data-cy="btn-preview">
+            <b-tooltip :label="$t('templates.preview')" type="is-dark">
+              <b-icon icon="file-find-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="#" @click.prevent="showEditForm(props.row)" data-cy="btn-edit">
+            <b-tooltip :label="$t('globals.buttons.edit')" type="is-dark">
+              <b-icon icon="pencil-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a href="" @click.prevent="$utils.prompt(`Clone template`,
+              { placeholder: 'Name', value: `Copy of ${props.row.name}`},
+              (name) => cloneTemplate(name, props.row))"
+              data-cy="btn-clone">
+            <b-tooltip :label="$t('globals.buttons.clone')" type="is-dark">
+              <b-icon icon="file-multiple-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <a v-if="!props.row.isDefault" href="#"
+            @click.prevent="$utils.confirm(null, () => makeTemplateDefault(props.row))"
+            data-cy="btn-set-default">
+            <b-tooltip :label="$t('templates.makeDefault')" type="is-dark">
+              <b-icon icon="check-circle-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <span v-else class="a has-text-grey-light">
+              <b-icon icon="check-circle-outline" size="is-small" />
+          </span>
+
+          <a v-if="!props.row.isDefault" href="#"
+            @click.prevent="$utils.confirm(null, () => deleteTemplate(props.row))"
+            data-cy="btn-delete">
+            <b-tooltip :label="$t('globals.buttons.delete')" type="is-dark">
+              <b-icon icon="trash-can-outline" size="is-small" />
+            </b-tooltip>
+          </a>
+          <span v-else class="a has-text-grey-light">
+              <b-icon icon="trash-can-outline" size="is-small" />
+          </span>
+        </div>
+      </b-table-column>
+
+      <template #empty v-if="!loading.templates">
+        <empty-placeholder />
+      </template>
     </b-table>
     </b-table>
 
 
     <!-- Add / edit form modal -->
     <!-- Add / edit form modal -->

+ 15 - 7
frontend/vue.config.js

@@ -15,12 +15,20 @@ module.exports = {
   productionSourceMap: false,
   productionSourceMap: false,
   filenameHashing: true,
   filenameHashing: true,
 
 
-	devServer: {
+  css: {
+    loaderOptions: {
+      sass: {
+        implementation: require('sass'), // This line must in sass option
+      },
+    },
+  },
+
+  devServer: {
     port: process.env.LISTMONK_FRONTEND_PORT || 8080,
     port: process.env.LISTMONK_FRONTEND_PORT || 8080,
-		proxy: {
-			'^/api': {
-				target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000'
-			}
-		}
-	}
+    proxy: {
+      '^/api': {
+        target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000'
+      }
+    }
+  }
 };
 };

Разница между файлами не показана из-за своего большого размера
+ 724 - 453
frontend/yarn.lock


Некоторые файлы не были показаны из-за большого количества измененных файлов