Compare commits

...
Author SHA1 Message Date
Pavel MineevandUmputun 8d95baa70f chore: add resize observer mock 2022-12-05 12:03:36 -06:00
Pavel MineevandUmputun 050bce163a refac: use resize observer instead mutation observer in order to resize iframe 2022-12-05 12:03:36 -06:00
Pavel MineevandUmputun 68b84683d0 fix: update iframe size on textarea size change 2022-12-03 12:08:00 -06:00
Pavel MineevandUmputun 6fe373d540 fix: update fe contributing documentation and get dev commands in order 2022-11-30 01:19:51 -06:00
Pavel MineevandUmputun ba19bcc729 fix: update iframe size on mount 2022-11-29 18:12:14 -06:00
Dmitry VerkhoturovandUmputun f1b65db2c7 prefer unused PreferServerCipherSuites param
This is not used since 1.17,
https://github.com/golang/go/commit/9d0819b27ca248f9949e7cf6bf7cb9fe7cf574e8
2022-11-13 12:02:40 -06:00
Dmitry VerkhoturovandUmputun f5f287ef06 bump actions/setup-go to v3 to resolve node12 deprecation note 2022-11-13 12:00:54 -06:00
Igor KlipachandUmputun f161e6033c Removed duplicated linter 'megacheck' 2022-11-13 11:54:26 -06:00
Dmitry VerkhoturovandUmputun c86bff8811 remove duplicate type definitions from function signatures
I haven't found a linter for these, so I had to catch these manually.
I found #757 to fix one of these, and I thought it would be good
to fix everything at once.
2022-11-13 11:51:51 -06:00
Dmitry VerkhoturovandUmputun 596b1045bd allow dots in site id during email user validation 2022-11-03 16:31:00 -05:00
Dmitry VerkhoturovandUmputun 907ca2b590 remove the only generic logger usage
github.com/go-pkgz/lgr should be used instead for consistency
with the rest of the code
2022-11-03 16:30:10 -05:00
Dmitry VerkhoturovandUmputun f1b5469b83 bump github actions versions
Old ones produce warnings due to deprecation of NodeJS 12
2022-11-03 15:20:30 -05:00
Denis HananeinandUmputun 984fbde540 Support no_footer option 2022-10-25 21:42:08 -05:00
Dmitry VerkhoturovandUmputun 2bdc05dd47 remove unused entrypoint.sh
It should be removed after changes in 141c75401.
2022-10-23 12:55:34 -05:00
Dmitry VerkhoturovandUmputun d2ea572abf fix "it's" used in place of "its" 2022-10-20 14:18:21 -05:00
Dmitry VerkhoturovandUmputun 8d5c4cd578 make docker build work on tag again 2022-10-20 14:16:38 -05:00
31 changed files with 105 additions and 101 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
DEBUG: ${{secrets.DEBUG}}
- name: install go
uses: actions/setup-go@v2
uses: actions/setup-go@v3
with:
go-version: 1.17
+2 -3
View File
@@ -3,7 +3,6 @@ name: build
on:
push:
branches:
- master
tags:
paths:
- ".github/workflows/ci-build.yml"
@@ -33,11 +32,11 @@ jobs:
- uses: actions/checkout@v3
- name: set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: available platforms
run: echo ${{ steps.buildx.outputs.platforms }}
+2 -2
View File
@@ -23,11 +23,11 @@ jobs:
uses: actions/checkout@v3
- name: set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: available platforms
run: echo ${{ steps.buildx.outputs.platforms }}
+2 -4
View File
@@ -96,20 +96,18 @@ LABEL org.opencontainers.image.authors="Umputun <umputun@gmail.com>" \
WORKDIR /srv
ADD docker-init.sh /entrypoint.sh
COPY docker-init.sh /srv/init.sh
ADD backend/scripts/backup.sh /usr/local/bin/backup
ADD backend/scripts/restore.sh /usr/local/bin/restore
ADD backend/scripts/import.sh /usr/local/bin/import
RUN chmod +x /entrypoint.sh /usr/local/bin/backup /usr/local/bin/restore /usr/local/bin/import
RUN chmod +x /srv/init.sh /usr/local/bin/backup /usr/local/bin/restore /usr/local/bin/import
COPY --from=build-backend /build/backend/remark42 /srv/remark42
COPY --from=build-frontend /srv/frontend/apps/remark42/public/ /srv/web/
COPY docker-init.sh /srv/init.sh
RUN chown -R app:app /srv
RUN ln -s /srv/remark42 /usr/bin/remark42
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD curl --fail http://localhost:8080/ping || exit 1
RUN chmod +x /srv/init.sh
CMD ["/srv/remark42", "server"]
-1
View File
@@ -35,7 +35,6 @@ linters:
- revive
- govet
- unconvert
- megacheck
- gas
- gocyclo
- dupl
@@ -8,11 +8,11 @@ package accessor
import (
"fmt"
"log"
"sort"
"sync"
"time"
log "github.com/go-pkgz/lgr"
"github.com/umputun/remark42/backend/app/store"
"github.com/umputun/remark42/backend/app/store/engine"
)
@@ -267,7 +267,7 @@ func (m *MemData) ListFlags(req engine.FlagRequest) (res []interface{}, err erro
return res, nil
case engine.Blocked:
log.Printf("%+v", m.metaUsers)
log.Printf("[INFO] metaUsers: %+v", m.metaUsers)
for _, u := range m.metaUsers {
if u.SiteID == req.Locator.SiteID && u.Blocked && u.BlockedUntil.After(time.Now()) {
res = append(res, store.BlockedUser{ID: u.UserID, Until: u.BlockedUntil})
+1 -1
View File
@@ -83,7 +83,7 @@ func (ab AutoBackup) removeOldBackupFiles() {
backFiles = append(backFiles, info)
}
}
sort.Slice(backFiles, func(i int, j int) bool { return backFiles[i].Name() < backFiles[j].Name() })
sort.Slice(backFiles, func(i, j int) bool { return backFiles[i].Name() < backFiles[j].Name() })
if len(backFiles) > ab.KeepMax {
for i := 0; i < len(backFiles)-ab.KeepMax; i++ {
+1 -1
View File
@@ -38,7 +38,7 @@ type MapperMaker func(reader io.Reader) (Mapper, error)
type Store interface {
Create(comment store.Comment) (commentID string, err error)
Find(locator store.Locator, sort string, user store.User) ([]store.Comment, error)
List(siteID string, limit int, skip int) ([]store.PostInfo, error)
List(siteID string, limit, skip int) ([]store.PostInfo, error)
DeleteAll(siteID string) error
Metas(siteID string) (umetas []service.UserMetaData, pmetas []service.PostMetaData, err error)
SetMetas(siteID string, umetas []service.UserMetaData, pmetas []service.PostMetaData) error
+2 -2
View File
@@ -34,8 +34,8 @@ type Destination interface {
// Store defines the minimal interface accessing stored comments used by notifier
type Store interface {
Get(locator store.Locator, id string, user store.User) (store.Comment, error)
GetUserEmail(siteID string, userID string) (string, error)
GetUserTelegram(siteID string, userID string) (string, error)
GetUserEmail(siteID, userID string) (string, error)
GetUserTelegram(siteID, userID string) (string, error)
}
// used for email and telegram retrieval from user details
+5 -5
View File
@@ -29,15 +29,15 @@ type admin struct {
type adminStore interface {
Delete(locator store.Locator, commentID string, mode store.DeleteMode) error
DeleteUser(siteID string, userID string, mode store.DeleteMode) error
DeleteUserDetail(siteID string, userID string, detail engine.UserDetail) error
DeleteUser(siteID, userID string, mode store.DeleteMode) error
DeleteUserDetail(siteID, userID string, detail engine.UserDetail) error
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
IsBlocked(siteID string, userID string) bool
SetBlock(siteID string, userID string, status bool, ttl time.Duration) error
IsBlocked(siteID, userID string) bool
SetBlock(siteID, userID string, status bool, ttl time.Duration) error
BlockedUsers(siteID string) ([]store.BlockedUser, error)
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error)
SetTitle(locator store.Locator, commentID string) (comment store.Comment, err error)
SetVerified(siteID string, userID string, status bool) error
SetVerified(siteID, userID string, status bool) error
SetReadOnly(locator store.Locator, status bool) error
SetPin(locator store.Locator, commentID string, status bool) error
}
+1 -1
View File
@@ -651,7 +651,7 @@ func subscribersOnly(enable bool) func(http.Handler) http.Handler {
func validEmailAuth() func(http.Handler) http.Handler {
reUser := regexp.MustCompile(`^[\p{L}\d\s_]{4,64}$`) // matches ui side validation, adding min/max limitation
reSite := regexp.MustCompile(`^[a-zA-Z\d\s_-]{1,64}$`)
reSite := regexp.MustCompile(`^[a-zA-Z\d\s_.-]{1,64}$`)
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
+7 -7
View File
@@ -58,15 +58,15 @@ type privStore interface {
Vote(req service.VoteReq) (comment store.Comment, err error)
Get(locator store.Locator, commentID string, user store.User) (store.Comment, error)
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
GetUserEmail(siteID string, userID string) (string, error)
SetUserEmail(siteID string, userID string, value string) (string, error)
GetUserTelegram(siteID string, userID string) (string, error)
SetUserTelegram(siteID string, userID string, value string) (string, error)
DeleteUserDetail(siteID string, userID string, detail engine.UserDetail) error
GetUserEmail(siteID, userID string) (string, error)
SetUserEmail(siteID, userID, value string) (string, error)
GetUserTelegram(siteID, userID string) (string, error)
SetUserTelegram(siteID, userID, value string) (string, error)
DeleteUserDetail(siteID, userID string, detail engine.UserDetail) error
ValidateComment(c *store.Comment) error
IsVerified(siteID string, userID string) bool
IsVerified(siteID, userID string) bool
IsReadOnly(locator store.Locator) bool
IsBlocked(siteID string, userID string) bool
IsBlocked(siteID, userID string) bool
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error)
}
+1 -1
View File
@@ -40,7 +40,7 @@ type pubStore interface {
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
UserCount(siteID, userID string) (int, error)
Count(locator store.Locator) (int, error)
List(siteID string, limit int, skip int) ([]store.PostInfo, error)
List(siteID string, limit, skip int) ([]store.PostInfo, error)
Info(locator store.Locator, readonlyAge int) (store.PostInfo, error)
ValidateComment(c *store.Comment) error
+1 -1
View File
@@ -378,7 +378,7 @@ func Test_validEmailAuth(t *testing.T) {
status int
}{
{"/auth/email/login?site=remark42&address=umputun%example.com&user=someone", http.StatusOK},
{"/auth/email/login?site=site-with-dash_and_underscore&address=umputun%example.com&user=someone", http.StatusOK},
{"/auth/email/login?site=site-with-dash_and_underscore-and.dot&address=umputun%example.com&user=someone", http.StatusOK},
{"/auth/email/login?site=remark42&address=umputun%example.com&user=someone+blah", http.StatusOK},
{"/auth/email/login?site=remark42&address=umputun%example.com&user=Евгений+Умпутун", http.StatusOK},
{"/auth/email/login?site=remark42&address=umputun%example.com&user=12", http.StatusForbidden},
-4
View File
@@ -111,15 +111,11 @@ func (s *Rest) getRemarkHost() string {
func (s *Rest) makeTLSConfig() *tls.Config {
return &tls.Config{
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
// tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
// tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
// tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
},
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{
+1 -1
View File
@@ -78,5 +78,5 @@ func (s *StaticStore) Enabled(site string) (ok bool, err error) {
return false, nil
}
// OnEvent doesn nothing for StaticStore
// OnEvent does nothing for StaticStore
func (s *StaticStore) OnEvent(_ string, _ EventType) error { return nil }
+10 -10
View File
@@ -16,15 +16,15 @@ import (
// BoltDB implements store.Interface, represents multiple sites with multiplexing to different bolt dbs. Thread safe.
// there are 6 types of top-level buckets:
// - comments for post in "posts" top-level bucket. Each url (post) makes its own bucket and each k:v pair is commentID:comment
// - history of all comments. They all in a single "last" bucket (per site) and key is defined by ref struct as ts+commentID
// value is not full comment but a reference combined from post-url+commentID
// - user to comment references in "users" bucket. It used to get comments for user. Key is userID and value
// is a nested bucket named userID with kv as ts:reference
// - users details in "user_details" bucket. Key is userID, value - UserDetailEntry
// - blocking info sits in "block" bucket. Key is userID, value - ts
// - counts per post to keep number of comments. Key is post url, value - count
// - readonly per post to keep status of manually set RO posts. Key is post url, value - ts
// - comments for post in "posts" top-level bucket. Each url (post) makes its own bucket and each k:v pair is commentID:comment
// - history of all comments. They all in a single "last" bucket (per site) and key is defined by ref struct as ts+commentID
// value is not full comment but a reference combined from post-url+commentID
// - user to comment references in "users" bucket. It used to get comments for user. Key is userID and value
// is a nested bucket named userID with kv as ts:reference
// - users details in "user_details" bucket. Key is userID, value - UserDetailEntry
// - blocking info sits in "block" bucket. Key is userID, value - ts
// - counts per post to keep number of comments. Key is post url, value - count
// - readonly per post to keep status of manually set RO posts. Key is post url, value - ts
type BoltDB struct {
dbs map[string]*bolt.DB
}
@@ -780,7 +780,7 @@ func (b *BoltDB) deleteUserDetail(bdb *bolt.DB, userID string, userDetail UserDe
}
return bdb.Update(func(tx *bolt.Tx) error {
// updated entry is not empty and we need to store it's updated copy
// updated entry is not empty and we need to store its updated copy
err := b.save(tx.Bucket([]byte(userDetailsBucketName)), userID, entry)
if err != nil {
return fmt.Errorf("failed to update detail %s for %s: %w", userDetail, userID, err)
+2 -2
View File
@@ -180,7 +180,7 @@ func TestFsStore_Cleanup(t *testing.T) {
svc, teardown := prepareImageTest(t)
defer teardown()
save := func(file string, user string) (filePath string) {
save := func(file, user string) (filePath string) {
id := path.Join(user, file)
err := svc.Save(id, gopherPNGBytes())
require.NoError(t, err)
@@ -227,7 +227,7 @@ func TestFsStore_Cleanup(t *testing.T) {
_, err = os.Stat(img2)
assert.Error(t, err, "no file on staging anymore")
_, err = os.Stat(img3)
assert.NoError(t, err, "third image is still on staging because it's cleanup timer was reset")
assert.NoError(t, err, "third image is still on staging because its cleanup timer was reset")
err = svc.ResetCleanupTimer("unknown_image.png")
assert.Error(t, err)
+3 -3
View File
@@ -496,7 +496,7 @@ func (s *DataStore) EditComment(locator store.Locator, commentID string, req Edi
if e := s.AdminStore.OnEvent(comment.Locator.SiteID, admin.EvDelete); e != nil {
log.Printf("[WARN] failed to send delete event, %s", e)
}
// clean up the comment and it's parent from cache, so that
// clean up the comment and its parent from cache, so that
// after cleaning up the child, parent won't be stuck non-deletable till cache expires
if s.repliesCache.LoadingCache != nil {
s.repliesCache.Delete(commentID)
@@ -745,12 +745,12 @@ func (s *DataStore) Delete(locator store.Locator, commentID string, mode store.D
if e := s.AdminStore.OnEvent(locator.SiteID, admin.EvDelete); e != nil {
log.Printf("[WARN] failed to send delete event, %s", e)
}
// get comment to learn it's parent ID
// get comment to learn its parent ID
comment, err := s.Engine.Get(engine.GetRequest{Locator: locator, CommentID: commentID})
if err != nil {
return err
}
// clean up the comment and it's parent from cache, so that
// clean up the comment and its parent from cache, so that
// after cleaning up the child, parent won't be stuck non-deletable till cache expires
if s.repliesCache.LoadingCache != nil {
s.repliesCache.Delete(commentID)
+2 -2
View File
@@ -14,7 +14,7 @@ import (
func TestMakeTree(t *testing.T) {
loc := store.Locator{URL: "url", SiteID: "site"}
ts := func(min int, sec int) time.Time { return time.Date(2017, 12, 25, 19, min, sec, 0, time.UTC) }
ts := func(min, sec int) time.Time { return time.Date(2017, 12, 25, 19, min, sec, 0, time.UTC) }
// unsorted by purpose
comments := []store.Comment{
@@ -54,7 +54,7 @@ func TestMakeTree(t *testing.T) {
func TestMakeEmptySubtree(t *testing.T) {
loc := store.Locator{URL: "url", SiteID: "site"}
ts := func(min int, sec int) time.Time { return time.Date(2017, 12, 25, 19, min, sec, 0, time.UTC) }
ts := func(min, sec int) time.Time { return time.Date(2017, 12, 25, 19, min, sec, 0, time.UTC) }
// unsorted by purpose
comments := []store.Comment{
@@ -0,0 +1,11 @@
global.ResizeObserver = class ResizeObserver {
observe() {
// do nothing
}
unobserve() {
// do nothing
}
disconnect() {
// do nothing
}
};
@@ -76,11 +76,11 @@ export function useDropdown(disableClosing?: boolean) {
handleChangeIframeSize(dropdownElement);
const observer = new MutationObserver(() => {
const observer = new ResizeObserver(() => {
handleChangeIframeSize(dropdownElement);
});
observer.observe(dropdownElement, { attributes: true, childList: true, subtree: true });
observer.observe(dropdownElement);
return () => {
document.body.style.removeProperty('min-height');
@@ -13,6 +13,7 @@
line-height: 1.4;
border: 0;
resize: none;
overflow: hidden; /* prevent scrollbar from appearing */
backface-visibility: hidden; /* let's try to fix blinking in Safari */
transform: translateZ(0); /* let's try to fix blinking in Safari, again */
@@ -1,5 +1,5 @@
import { h, Component, Fragment } from 'preact';
import { useEffect, useRef } from 'preact/hooks';
import { useEffect } from 'preact/hooks';
import { useSelector } from 'react-redux';
import b from 'bem-react-helper';
import { IntlShape, useIntl, FormattedMessage, defineMessages } from 'react-intl';
@@ -42,6 +42,13 @@ import { setCollapse } from 'store/thread/actions';
import styles from './root.module.css';
/**
* Sends size of the iframe to parent window
*/
export function updateIframeHeight() {
postMessageToParent({ height: document.body.offsetHeight });
}
const mapStateToProps = (state: StoreState) => ({
sort: state.comments.sort,
isCommentsLoading: state.comments.isFetching,
@@ -287,10 +294,6 @@ export class Root extends Component<Props, State> {
}
}
function updateIframeHeight() {
postMessageToParent({ height: document.body.offsetHeight });
}
interface CommentsProps {
isLoading: boolean;
topComments: string[];
@@ -298,34 +301,12 @@ interface CommentsProps {
showMore(): void;
}
function Comments({ isLoading, topComments, commentsShown, showMore }: CommentsProps) {
const rootRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!rootRef.current) {
return;
}
// TODO: throttle updates
const observer = new MutationObserver(() => {
updateIframeHeight();
// a hacky way to force iframe height update when new image is rendered and loaded
rootRef.current?.querySelectorAll('img').forEach((img) => {
img.addEventListener('load', updateIframeHeight);
});
});
observer.observe(rootRef.current, { attributes: true, childList: true, subtree: true });
return () => observer.disconnect();
}, []);
const renderComments =
IS_MOBILE && commentsShown < topComments.length ? topComments.slice(0, commentsShown) : topComments;
const isShowMoreButtonVisible = IS_MOBILE && commentsShown < topComments.length;
return (
<div className="root__threads" role="list" ref={rootRef}>
<div className="root__threads" role="list">
{isLoading ? (
<Preloader className="root__preloader" />
) : (
@@ -351,22 +332,36 @@ const CopyrightLink = (title: string) => (
</a>
);
const Copyright = () => (
<p className="root__copyright" role="contentinfo">
<FormattedMessage id="root.powered-by" defaultMessage="Powered by <a>Remark42</a>" values={{ a: CopyrightLink }} />
</p>
);
/** Root component connected to redux */
export function ConnectedRoot() {
const intl = useIntl();
const props = useSelector(mapStateToProps);
const actions = useActions(boundActions);
useEffect(() => {
const observer = new ResizeObserver(updateIframeHeight);
updateIframeHeight();
observer.observe(document.body);
return () => observer.disconnect();
}, []);
if (!window.remark_config) {
throw new Error('Remark42: Config object is undefined');
}
const { no_footer } = window.remark_config;
return (
<div className={clsx(b('root', {}, { theme: props.theme }), props.theme)}>
<Root {...props} {...actions} intl={intl} />
<p className="root__copyright" role="contentinfo">
<FormattedMessage
id="root.powered-by"
defaultMessage="Powered by <a>Remark42</a>"
values={{ a: CopyrightLink }}
/>
</p>
{!no_footer && <Copyright />}
</div>
);
}
@@ -2,13 +2,13 @@ import { h, JSX } from 'preact';
import { forwardRef } from 'preact/compat';
import { useEffect, useRef } from 'preact/hooks';
function autoResize(textarea: HTMLTextAreaElement) {
function autoResize(textarea: HTMLTextAreaElement, onResize?: () => void) {
textarea.style.height = '';
textarea.style.height = `${textarea.scrollHeight}px`;
}
type Props = Omit<JSX.HTMLAttributes<HTMLTextAreaElement>, 'onInput'> & {
onInput?: (evt: JSX.TargetedEvent<HTMLTextAreaElement, Event>) => void;
onInput?(evt: JSX.TargetedEvent<HTMLTextAreaElement, Event>): void;
};
export const TextareaAutosize = forwardRef<HTMLTextAreaElement, Props>(({ onInput, value, ...props }, externalRef) => {
+1
View File
@@ -13,6 +13,7 @@ type RemarkConfig = {
max_last_comments?: number;
__colors__?: Record<string, string>;
simple_view?: boolean;
no_footer?: boolean;
};
declare global {
+1
View File
@@ -33,6 +33,7 @@ const config: Config = {
setupFilesAfterEnv: [
'<rootDir>/app/__mocks__/fetch.ts',
'<rootDir>/app/__mocks__/localstorage.ts',
'<rootDir>/app/__mocks__/resize-observer.ts',
'<rootDir>/app/__stubs__/remark-config.ts',
'<rootDir>/app/__stubs__/static-config.ts',
],
+3 -2
View File
@@ -3,8 +3,9 @@
"version": "0.16.0",
"license": "MIT",
"scripts": {
"dev": "cross-env REMARK_URL=http://127.0.0.1:8080 webpack serve --mode development",
"start": "cross-env REMARK_API_BASE_URL=https://demo.remark42.com webpack serve --mode development",
"dev": "cross-env REMARK_URL=http://127.0.0.1:8080 pnpm dev:custom",
"dev:demo": "cross-env REMARK_API_BASE_URL=https://demo.remark42.com pnpm dev:custom",
"dev:custom": "webpack serve --mode development",
"build": "webpack --mode production",
"build:analyze": "webpack --mode production --analyze",
"lint": "run-p lint:*",
+1 -1
View File
@@ -11,7 +11,7 @@
"scripts": {
"prepare": "if [ -z \"$CI\" ]; then cd .. && husky install frontend/.husky; else echo \"Skip Husky Hooks\"; fi",
"lint-staged": "lint-staged",
"dev:remark42": "turbo run dev --filter=@remark42/app",
"dev:app": "turbo run dev --filter=@remark42/app",
"test:api": "turbo run test --filter=@remark42/api",
"coverage:api": "turbo run coverage --filter=@remark42/api",
"type-check:api": "turbo run type-check --filter=@remark42/api",
@@ -20,6 +20,7 @@ title: Frontend Configuration
- **`show_email_subscription`**`: boolean` (optional, `true` by default) enables email subscription feature in interface when enable it from backend side, if you set this param in `false` you will get notifications email notifications as admin but your users won't have interface for subscription
- **`show_rss_subscription`**`: boolean` (optional, `true` by default) enables RSS subscription feature in interface
- **`simple_view`**`: boolean` (optional, `false` by default) overrides the parameter from the backend minimized UI with basic info only
- **`no_footer`**`: boolean` (optional, `false` by default) hides footer with signatue and links to remark42
Example with all of the params:
@@ -34,7 +35,8 @@ Example with all of the params:
page_title: 'My custom title for a page',
locale: 'es',
show_email_subscription: false,
simple_view: true
simple_view: true,
no_footer: false
}
</script>
```
+4 -4
View File
@@ -12,7 +12,7 @@ TLDR: Preact replicates React API and compatible with its libraries.
:::
In order to inject Remark42 widgets into websites we use `iframe` and `postMessage` for communication between a site and the widget.
Simple widgets like [counter widget](https://remark42.com/docs/configuration/frontend/#counter-widget) can be injected as a script because it doesn't have it's own interface.
Simple widgets like [counter widget](https://remark42.com/docs/configuration/frontend/#counter-widget) can be injected as a script because it doesn't have its own interface.
While development we setup environment which imitates real world example. We serve the page which uses Remark42 config and inject all of the widgets on it. You can check it on our [demo site](https://demo.remark42.com/web/). After successful installation you should have the same page running locally.
@@ -36,12 +36,12 @@ Please use `127.0.0.1` and not `localhost` to access the server; otherwise, CORS
You can run frontend against demo instance of Remark42. This method of running Remark42 frontend code is preferred when you make a translation or visual adjustments that are easy to see without extensive testing. For this method we use our demo instance of Remark42 served on https://demo.remark42.com
For local development mode with Hot Reloading, use `pnpm start:app`. In this case, `webpack` will serve files using `webpack-dev-server` on `127.0.0.1:9000`. By visiting <http://127.0.0.1:9000/web/>, you will get a page with the main comments' widget communicating with a demo server backend running on `https://demo.remark42.com`. But you will not be able to log in with any OAuth providers due to security reasons.
For local development mode with Hot Reloading, use `pnpm dev:app`. In this case, `webpack` will serve files using `webpack-dev-server` on `127.0.0.1:9000`. By visiting <http://127.0.0.1:9000/web/>, you will get a page with the main comments' widget communicating with a demo server backend running on `https://demo.remark42.com`. But you will not be able to log in with any OAuth providers due to security reasons.
You can attach the frontend to the locally running backend by providing the `REMARK_URL` environment variable.
You can attach the frontend to the locally running backend from `frontend/apps/remark42` folder and providing the `REMARK_URL` environment variable.
```shell
npx cross-env REMARK_URL=http://127.0.0.1:8080 pnpm dev:app
npx cross-env REMARK_URL=http://127.0.0.1:8080 pnpm dev:custom
```
::: note